Skip to content

Commit 0d34468

Browse files
authored
STG100 - Datalake Principal Bound Identity SAS (#46445)
* adding implementation and happy path test * adding more tests and recordings, and moving token extraction to common * re-adding recordings after pull * fixing string to sign test * forgot that token can't be recorded * reverting assets and moving query param * adding old assets tag back because im silly * whole feature * adding more tests and consolidating string to sign logic * fixing test that was unocovered through the fix to liveTestScenarioWithRetry * removing change in blobsasimplutil
1 parent 08284c0 commit 0d34468

File tree

7 files changed

+433
-40
lines changed

7 files changed

+433
-40
lines changed

sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/implementation/util/DataLakeSasImplUtil.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ public class DataLakeSasImplUtil {
9292

9393
private String encryptionScope;
9494

95+
private String delegatedUserObjectId;
96+
9597
/**
9698
* Creates a new {@link DataLakeSasImplUtil} with the specified parameters
9799
*
@@ -108,7 +110,7 @@ public DataLakeSasImplUtil(DataLakeServiceSasSignatureValues sasValues, String f
108110
* @param sasValues {@link DataLakeServiceSasSignatureValues}
109111
* @param fileSystemName The file system name
110112
* @param pathName The path name
111-
* @param isDirectory Whether or not the path points to a directory.
113+
* @param isDirectory Whether the path points to a directory.
112114
*/
113115
public DataLakeSasImplUtil(DataLakeServiceSasSignatureValues sasValues, String fileSystemName, String pathName,
114116
boolean isDirectory) {
@@ -131,6 +133,7 @@ public DataLakeSasImplUtil(DataLakeServiceSasSignatureValues sasValues, String f
131133
this.correlationId = sasValues.getCorrelationId();
132134
this.isDirectory = isDirectory;
133135
this.encryptionScope = sasValues.getEncryptionScope();
136+
this.delegatedUserObjectId = sasValues.getDelegatedUserObjectId();
134137
}
135138

136139
/**
@@ -255,6 +258,8 @@ private String encode(UserDelegationKey userDelegationKey, String signature) {
255258
this.authorizedAadObjectId);
256259
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_AGENT_OBJECT_ID, this.unauthorizedAadObjectId);
257260
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CORRELATION_ID, this.correlationId);
261+
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_DELEGATED_USER_OBJECT_ID,
262+
this.delegatedUserObjectId);
258263
}
259264

260265
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_RESOURCE, this.resource);
@@ -455,7 +460,7 @@ public String stringToSign(final UserDelegationKey key, String canonicalName) {
455460
this.authorizedAadObjectId == null ? "" : this.authorizedAadObjectId,
456461
this.unauthorizedAadObjectId == null ? "" : this.unauthorizedAadObjectId,
457462
this.correlationId == null ? "" : this.correlationId, "", /* new schema 2025-07-05 */
458-
"", /* new schema 2025-07-05 */
463+
this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId,
459464
this.sasIpRange == null ? "" : this.sasIpRange.toString(),
460465
this.protocol == null ? "" : this.protocol.toString(), VERSION, resource, "", /* Version segment. */
461466
this.encryptionScope == null ? "" : this.encryptionScope,

sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/sas/DataLakeServiceSasSignatureValues.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public final class DataLakeServiceSasSignatureValues {
4444
private String agentObjectId; /* suoid */
4545
private String correlationId;
4646
private String encryptionScope;
47+
private String delegatedUserObjectId;
4748

4849
/**
4950
* Creates an object with the specified expiry time and permissions
@@ -450,4 +451,28 @@ public DataLakeServiceSasSignatureValues setEncryptionScope(String encryptionSco
450451
this.encryptionScope = encryptionScope;
451452
return this;
452453
}
454+
455+
/**
456+
* Optional. Beginning in version 2025-07-05, this value specifies the Entra ID of the user that is authorized to
457+
* use the resulting SAS URL. The resulting SAS URL must be used in conjunction with an Entra ID token that has been
458+
* issued to the user specified in this value.
459+
*
460+
* @return The Entra ID of the user that is authorized to use the resulting SAS URL.
461+
*/
462+
public String getDelegatedUserObjectId() {
463+
return delegatedUserObjectId;
464+
}
465+
466+
/**
467+
* Optional. Beginning in version 2025-07-05, this value specifies the Entra ID of the user that is authorized to
468+
* use the resulting SAS URL. The resulting SAS URL must be used in conjunction with an Entra ID token that has been
469+
* issued to the user specified in this value.
470+
*
471+
* @param delegatedUserObjectId The Entra ID of the user that is authorized to use the resulting SAS URL.
472+
* @return the updated DataLakeServiceSasSignatureValues object
473+
*/
474+
public DataLakeServiceSasSignatureValues setDelegatedUserObjectId(String delegatedUserObjectId) {
475+
this.delegatedUserObjectId = delegatedUserObjectId;
476+
return this;
477+
}
453478
}

sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/DataLakeTestBase.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,4 +760,26 @@ public static void waitUntilPredicate(long delayMillis, int numberOfDelays, Supp
760760
sleepIfLiveTesting(delayMillis);
761761
}
762762
}
763+
764+
protected void liveTestScenarioWithRetry(Runnable runnable) {
765+
if (!interceptorManager.isLiveMode()) {
766+
runnable.run();
767+
return;
768+
}
769+
770+
int retry = 0;
771+
772+
// Try up to 5 times (4 retries + 1 final attempt)
773+
while (retry < 4) {
774+
try {
775+
runnable.run();
776+
return; // success
777+
} catch (Exception ex) {
778+
retry++;
779+
sleepIfRunningAgainstService(5000);
780+
}
781+
}
782+
// Final attempt (5th try)
783+
runnable.run();
784+
}
763785
}

sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/FileApiTest.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3192,24 +3192,6 @@ public void queryAC(OffsetDateTime modified, OffsetDateTime unmodified, String m
31923192
});
31933193
}
31943194

3195-
private void liveTestScenarioWithRetry(Runnable runnable) {
3196-
if (!interceptorManager.isLiveMode()) {
3197-
runnable.run();
3198-
return;
3199-
}
3200-
3201-
int retry = 0;
3202-
while (retry < 5) {
3203-
try {
3204-
runnable.run();
3205-
break;
3206-
} catch (Exception ex) {
3207-
retry++;
3208-
sleepIfRunningAgainstService(5000);
3209-
}
3210-
}
3211-
}
3212-
32133195
@RequiredServiceVersion(clazz = DataLakeServiceVersion.class, min = "2019-12-12")
32143196
@ParameterizedTest
32153197
@MethodSource("invalidModifiedMatchAndLeaseIdSupplier")

sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/FileAsyncApiTests.java

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3903,12 +3903,15 @@ public void queryInputOutputIA(boolean input, boolean output) {
39033903
FileQuerySerialization outSer = output ? ser : null;
39043904
String expression = "SELECT * from BlobStorage";
39053905

3906-
liveTestScenarioWithRetry(() -> {
3907-
StepVerifier.create(fc.queryWithResponse(
3908-
new FileQueryOptions(expression, new ByteArrayOutputStream()).setInputSerialization(inSer)
3909-
.setOutputSerialization(outSer)))
3910-
.verifyError(IllegalArgumentException.class);
3911-
});
3906+
FileQueryOptions options
3907+
= new FileQueryOptions(expression, new ByteArrayOutputStream()).setInputSerialization(inSer)
3908+
.setOutputSerialization(outSer);
3909+
3910+
// The IllegalArgumentException is thrown synchronously by the fc.queryWithResponse(options) method call itself,
3911+
// before StepVerifier.create() can subscribe to the Mono. StepVerifier.verifyError() is designed to verify an
3912+
// onError signal emitted by a reactive stream. It does not catch exceptions thrown during the assembly or
3913+
// creation of the stream.
3914+
assertThrows(IllegalArgumentException.class, () -> fc.queryWithResponse(options));
39123915
}
39133916

39143917
@RequiredServiceVersion(clazz = DataLakeServiceVersion.class, min = "2019-12-12")
@@ -3975,19 +3978,6 @@ public void queryAC(OffsetDateTime modified, OffsetDateTime unmodified, String m
39753978
});
39763979
}
39773980

3978-
private void liveTestScenarioWithRetry(Runnable runnable) {
3979-
int retry = 0;
3980-
while (retry < 5) {
3981-
try {
3982-
runnable.run();
3983-
break;
3984-
} catch (Exception ex) {
3985-
retry++;
3986-
sleepIfLiveTesting(5000);
3987-
}
3988-
}
3989-
}
3990-
39913981
@RequiredServiceVersion(clazz = DataLakeServiceVersion.class, min = "2019-12-12")
39923982
@ParameterizedTest
39933983
@MethodSource("invalidModifiedMatchAndLeaseIdSupplier")

0 commit comments

Comments
 (0)