From 7f8685026a94b4c7106920283639feee6cc81739 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Tue, 5 Aug 2025 13:43:27 -0700 Subject: [PATCH 01/14] adding implementation and happy path test --- .../implementation/util/BlobSasImplUtil.java | 30 +++++- .../sas/BlobServiceSasSignatureValues.java | 26 ++++++ .../com/azure/storage/blob/BlobTestBase.java | 8 +- .../azure/storage/blob/SasClientTests.java | 92 ++++++++++++++++++- .../common/implementation/Constants.java | 5 + .../common/sas/CommonSasQueryParameters.java | 18 ++++ 6 files changed, 172 insertions(+), 7 deletions(-) diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/util/BlobSasImplUtil.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/util/BlobSasImplUtil.java index 12caac54befa..7db639e75044 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/util/BlobSasImplUtil.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/util/BlobSasImplUtil.java @@ -95,6 +95,8 @@ public class BlobSasImplUtil { private String encryptionScope; + private String delegatedUserObjectId; + /** * Creates a new {@link BlobSasImplUtil} with the specified parameters * @@ -140,6 +142,7 @@ public BlobSasImplUtil(BlobServiceSasSignatureValues sasValues, String container this.authorizedAadObjectId = sasValues.getPreauthorizedAgentObjectId(); this.correlationId = sasValues.getCorrelationId(); this.encryptionScope = encryptionScope; + this.delegatedUserObjectId = sasValues.getDelegatedUserObjectId(); } /** @@ -266,6 +269,7 @@ private String encode(UserDelegationKey userDelegationKey, String signature) { tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_RESOURCE, this.resource); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNATURE, signature); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_DELEGATED_USER_OBJECT_ID, this.delegatedUserObjectId); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_ENCRYPTION_SCOPE, this.encryptionScope); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CACHE_CONTROL, this.cacheControl); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CONTENT_DISPOSITION, this.contentDisposition); @@ -438,7 +442,7 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) { this.contentEncoding == null ? "" : this.contentEncoding, this.contentLanguage == null ? "" : this.contentLanguage, this.contentType == null ? "" : this.contentType); - } else { + } else if (VERSION.compareTo(BlobServiceVersion.V2025_11_05.getVersion()) <= 0) { return String.join("\n", this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), this.expiryTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), @@ -462,6 +466,30 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) { this.contentEncoding == null ? "" : this.contentEncoding, this.contentLanguage == null ? "" : this.contentLanguage, this.contentType == null ? "" : this.contentType); + } else { + return String.join("\n", this.permissions == null ? "" : this.permissions, + this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), + this.expiryTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), + canonicalName, key.getSignedObjectId() == null ? "" : key.getSignedObjectId(), + key.getSignedTenantId() == null ? "" : key.getSignedTenantId(), + key.getSignedStart() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedStart()), + key.getSignedExpiry() == null + ? "" + : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedExpiry()), + key.getSignedService() == null ? "" : key.getSignedService(), + key.getSignedVersion() == null ? "" : key.getSignedVersion(), + this.authorizedAadObjectId == null ? "" : this.authorizedAadObjectId, + "", /* suoid - empty since this applies to HNS only accounts. */ + this.correlationId == null ? "" : this.correlationId, "", /* new schema 2025-07-05 */ + this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId, + this.sasIpRange == null ? "" : this.sasIpRange.toString(), + this.protocol == null ? "" : this.protocol.toString(), VERSION, resource, + versionSegment == null ? "" : versionSegment, this.encryptionScope == null ? "" : this.encryptionScope, + this.cacheControl == null ? "" : this.cacheControl, + this.contentDisposition == null ? "" : this.contentDisposition, + this.contentEncoding == null ? "" : this.contentEncoding, + this.contentLanguage == null ? "" : this.contentLanguage, + this.contentType == null ? "" : this.contentType); } } diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/sas/BlobServiceSasSignatureValues.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/sas/BlobServiceSasSignatureValues.java index 113672d9522c..829bac396031 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/sas/BlobServiceSasSignatureValues.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/sas/BlobServiceSasSignatureValues.java @@ -82,6 +82,7 @@ public final class BlobServiceSasSignatureValues { private String preauthorizedAgentObjectId; /* saoid */ private String correlationId; private String encryptionScope; + private String delegatedUserObjectId; /** * Creates an object with empty values for all fields. @@ -575,6 +576,30 @@ public BlobServiceSasSignatureValues setCorrelationId(String correlationId) { return this; } + /** + * Optional. Beginning in version 2025-07-05, this value specifies the Entra ID of the user that is authorized to + * use the resulting SAS URL. The resulting SAS URL must be used in conjunction with an Entra ID token that has been + * issued to the user specified in this value. + * + * @return The Entra ID of the user that is authorized to use the resulting SAS URL. + */ + public String getDelegatedUserObjectId() { + return delegatedUserObjectId; + } + + /** + * Optional. Beginning in version 2025-07-05, this value specifies the Entra ID of the user that is authorized to + * use the resulting SAS URL. The resulting SAS URL must be used in conjunction with an Entra ID token that has been + * issued to the user specified in this value. + * + * @param delegatedUserObjectId The Entra ID of the user that is authorized to use the resulting SAS URL. + * @return the updated BlobServiceSasSignatureValues object + */ + public BlobServiceSasSignatureValues setDelegatedUserObjectId(String delegatedUserObjectId) { + this.delegatedUserObjectId = delegatedUserObjectId; + return this; + } + /** * Uses an account's shared key credential to sign these signature values to produce the proper SAS query * parameters. @@ -754,6 +779,7 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) { key.getSignedExpiry() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedExpiry()), key.getSignedService() == null ? "" : key.getSignedService(), key.getSignedVersion() == null ? "" : key.getSignedVersion(), + this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId, this.sasIpRange == null ? "" : this.sasIpRange.toString(), this.protocol == null ? "" : this.protocol.toString(), VERSION_DEPRECATED_USER_DELEGATION_SAS_STRING_TO_SIGN, /* Pin down to version so old string to sign works. */ diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/BlobTestBase.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/BlobTestBase.java index ee19b6586f1c..2ade2966bab5 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/BlobTestBase.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/BlobTestBase.java @@ -772,15 +772,19 @@ protected void liveTestScenarioWithRetry(Runnable runnable) { } int retry = 0; - while (retry < 5) { + + // Try up to 4 times + while (retry < 4) { try { runnable.run(); - break; + return; // success } catch (Exception ex) { retry++; sleepIfRunningAgainstService(5000); } } + // Final attempt (5th try) + runnable.run(); } protected HttpPipelinePolicy getPerCallVersionPolicy() { diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java index ea2223591889..a8735887ab8c 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java @@ -3,7 +3,11 @@ package com.azure.storage.blob; +import com.azure.core.credential.AccessToken; import com.azure.core.credential.AzureSasCredential; +import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRequestContext; +import com.azure.core.http.rest.Response; import com.azure.core.util.Context; import com.azure.storage.blob.implementation.util.BlobSasImplUtil; import com.azure.storage.blob.models.BlobProperties; @@ -26,6 +30,7 @@ import com.azure.storage.common.sas.CommonSasQueryParameters; import com.azure.storage.common.sas.SasIpRange; import com.azure.storage.common.sas.SasProtocol; +import com.azure.storage.common.test.shared.StorageCommonTestUtils; import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -42,8 +47,11 @@ import java.time.ZoneOffset; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Stream; +import static java.util.Base64.getUrlDecoder; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -177,6 +185,34 @@ public void blobSasUserDelegation() { }); } + // RBAC replication lag + @Test + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") + public void blobSasUserDelegationDelegatedObjectId() { + liveTestScenarioWithRetry(() -> { + BlobSasPermission permissions = new BlobSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + String sas = sasClient.generateUserDelegationSas(sasValues, getUserDelegationInfo()); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. + BlockBlobClient client = instrument( + new BlobClientBuilder().endpoint(sasClient.getBlobUrl()).sasToken(sas).credential(tokenCredential)) + .buildClient() + .getBlockBlobClient(); + + Response response = client.getPropertiesWithResponse(null, null, Context.NONE); + assertResponseStatusCode(response, 200); + }); + } + @SuppressWarnings("deprecation") @Test public void blobSasSnapshot() { @@ -263,6 +299,46 @@ public void containerSasUserDelegation() { }); } + // RBAC replication lag + @Test + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") + public void containerSasUserDelegationDelegatedObjectId() { + liveTestScenarioWithRetry(() -> { + BlobContainerSasPermission permissions = new BlobContainerSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + String sas = cc.generateUserDelegationSas(sasValues, getUserDelegationInfo()); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. + BlobContainerClient client = instrument(new BlobContainerClientBuilder().endpoint(cc.getBlobContainerUrl()) + .sasToken(sas) + .credential(tokenCredential)).buildClient(); + + assertDoesNotThrow(() -> client.listBlobs().iterator().hasNext()); + }); + } + + private String getOidFromToken(TokenCredential credential) { + AccessToken accessToken + = credential.getTokenSync(new TokenRequestContext().addScopes("https://storage.azure.com/.default")); + String[] chunks = accessToken.getToken().split("\\."); + String payload = new String(getUrlDecoder().decode(chunks[1])); + + Pattern pattern = Pattern.compile("\"oid\":\"(.*?)\""); + Matcher matcher = pattern.matcher(payload); + if (matcher.find()) { + return matcher.group(1); + } + throw new RuntimeException("Could not find oid in token"); + } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2019-12-12") @Test public void blobSasTags() { @@ -334,7 +410,7 @@ public void containerSasTagsFail() { .setCreatePermission(true) .setDeletePermission(true) .setAddPermission(true); - /* No tags permission. */ + /* No tagsPermission. */ OffsetDateTime expiryTime = testResourceNamer.now().plusDays(1); BlobServiceSasSignatureValues sasValues = new BlobServiceSasSignatureValues(expiryTime, permissions); @@ -943,7 +1019,7 @@ public void blobSasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTim OffsetDateTime keyStart, OffsetDateTime keyExpiry, String keyService, String keyVersion, String keyValue, SasIpRange ipRange, SasProtocol protocol, String snapId, String cacheControl, String disposition, String encoding, String language, String type, String versionId, String saoid, String cid, - String encryptionScope, String expectedStringToSign) { + String encryptionScope, String delegatedUserObjectId, String expectedStringToSign) { OffsetDateTime e = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); BlobSasPermission p = new BlobSasPermission().setReadPermission(true); BlobServiceSasSignatureValues v = new BlobServiceSasSignatureValues(e, p); @@ -965,7 +1041,8 @@ public void blobSasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTim .setContentLanguage(language) .setContentType(type) .setPreauthorizedAgentObjectId(saoid) - .setCorrelationId(cid); + .setCorrelationId(cid) + .setDelegatedUserObjectId(delegatedUserObjectId); UserDelegationKey key = new UserDelegationKey().setSignedObjectId(keyOid) .setSignedTenantId(keyTid) @@ -1131,7 +1208,14 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\nencryptionScope\n\n\n\n\n")); + + "\nb\n\nencryptionScope\n\n\n\n\n"), + Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, + null, null, null, null, null, null, null, null, null, null, null, "delegatedOid", + "r\n\n" + + Constants.ISO_8601_UTC_DATE_FORMATTER + .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\ndelegatedOid\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n")); } @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2020-12-06") diff --git a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/Constants.java b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/Constants.java index d2d096f9f390..c98138550085 100644 --- a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/Constants.java +++ b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/Constants.java @@ -344,6 +344,11 @@ public static final class UrlConstants { */ public static final String SAS_SIGNATURE = "sig"; + /** + * The SAS signature parameter. + */ + public static final String SAS_DELEGATED_USER_OBJECT_ID = "sduoid"; + /** * The SAS encryption scope parameter. */ diff --git a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/sas/CommonSasQueryParameters.java b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/sas/CommonSasQueryParameters.java index aa54d3623836..25d2e0ec2404 100644 --- a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/sas/CommonSasQueryParameters.java +++ b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/sas/CommonSasQueryParameters.java @@ -12,6 +12,8 @@ import java.util.Map; import java.util.function.Function; +import static com.azure.storage.common.implementation.SasImplUtils.tryAppendQueryParameter; + /** * Represents the components that make up an Azure Storage SAS' query parameters. This type is not constructed directly * by the user; it is only generated by the URLParts type. NOTE: Instances of this class are immutable to ensure thread @@ -45,6 +47,7 @@ public class CommonSasQueryParameters { private final String unauthorizedObjectId; private final String correlationId; private final String encryptionScope; + private final String delegatedUserObjectId; /** * Creates a new {@link CommonSasQueryParameters} object. @@ -108,6 +111,8 @@ public CommonSasQueryParameters(Map queryParamsMap, boolean re removeSasParametersFromMap, Integer::parseInt); this.encryptionScope = getQueryParameter(queryParamsMap, Constants.UrlConstants.SAS_ENCRYPTION_SCOPE, removeSasParametersFromMap); + this.delegatedUserObjectId = getQueryParameter(queryParamsMap, + Constants.UrlConstants.SAS_DELEGATED_USER_OBJECT_ID, removeSasParametersFromMap); } /** @@ -168,6 +173,8 @@ public String encode() { SasImplUtils.tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); SasImplUtils.tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); SasImplUtils.tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNATURE, this.signature); + SasImplUtils.tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_DELEGATED_USER_OBJECT_ID, + this.delegatedUserObjectId); // Account SasImplUtils.tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SERVICES, this.services); @@ -466,4 +473,15 @@ public String getCorrelationId() { public String getEncryptionScope() { return encryptionScope; } + + /** + * Optional. Beginning in version 2025-07-05, this value specifies the Entra ID of the user that is authorized to + * use the resulting SAS URL. The resulting SAS URL must be used in conjunction with an Entra ID token that has been + * issued to the user specified in this value. + * + * @return The Entra ID of the user that is authorized to use the resulting SAS URL. + */ + public String getDelegatedUserObjectId() { + return delegatedUserObjectId; + } } From 860ee36a222e75f85450da81d9607274a171eb7a Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 6 Aug 2025 10:48:02 -0700 Subject: [PATCH 02/14] adding more tests and recordings, and moving token extraction to common --- sdk/storage/azure-storage-blob/assets.json | 2 +- .../storage/blob/SasAsyncClientTests.java | 136 ++++++++++++++++++ .../azure/storage/blob/SasClientTests.java | 72 +++++++--- .../test/shared/StorageCommonTestUtils.java | 25 ++++ 4 files changed, 218 insertions(+), 17 deletions(-) diff --git a/sdk/storage/azure-storage-blob/assets.json b/sdk/storage/azure-storage-blob/assets.json index 29bcfdae5bf9..7c677b44bf55 100644 --- a/sdk/storage/azure-storage-blob/assets.json +++ b/sdk/storage/azure-storage-blob/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/storage/azure-storage-blob", - "Tag": "java/storage/azure-storage-blob_80c07fe827" + "Tag": "java/storage/azure-storage-blob_3bf81530cc" } diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java index 5cfba2918022..d095ed2c28f7 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java @@ -4,10 +4,13 @@ package com.azure.storage.blob; import com.azure.core.credential.AzureSasCredential; +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.rest.Response; import com.azure.core.util.Context; import com.azure.core.util.FluxUtil; import com.azure.storage.blob.implementation.util.BlobSasImplUtil; import com.azure.storage.blob.models.BlobContainerProperties; +import com.azure.storage.blob.models.BlobErrorCode; import com.azure.storage.blob.models.BlobItem; import com.azure.storage.blob.models.BlobProperties; import com.azure.storage.blob.models.BlobStorageException; @@ -30,6 +33,7 @@ import com.azure.storage.common.sas.CommonSasQueryParameters; import com.azure.storage.common.sas.SasIpRange; import com.azure.storage.common.sas.SasProtocol; +import com.azure.storage.common.test.shared.StorageCommonTestUtils; import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -53,6 +57,7 @@ import java.util.Map; import java.util.stream.Stream; +import static com.azure.storage.common.test.shared.StorageCommonTestUtils.getOidFromToken; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -196,6 +201,72 @@ public void blobSasUserDelegation() { }); } + // RBAC replication lag + @Test + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") + public void blobSasUserDelegationDelegatedObjectId() { + liveTestScenarioWithRetry(() -> { + BlobSasPermission permissions = new BlobSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + + Mono> response = getUserDelegationInfo().flatMap(key -> { + String sas = sasClient.generateUserDelegationSas(sasValues, key); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. + BlockBlobAsyncClient client = instrument( + new BlobClientBuilder().endpoint(sasClient.getBlobUrl()).sasToken(sas).credential(tokenCredential)) + .buildAsyncClient() + .getBlockBlobAsyncClient(); + + return client.getPropertiesWithResponse(null); + }); + + StepVerifier.create(response).assertNext(r -> assertResponseStatusCode(r, 200)).verifyComplete(); + }); + } + + // RBAC replication lag + @Test + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") + public void blobSasUserDelegationDelegatedObjectIdFail() { + liveTestScenarioWithRetry(() -> { + BlobSasPermission permissions = new BlobSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + + Mono> response = getUserDelegationInfo().flatMap(key -> { + String sas = sasClient.generateUserDelegationSas(sasValues, key); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. Token credential is not provided here, so the request should fail. + BlockBlobAsyncClient client + = instrument(new BlobClientBuilder().endpoint(sasClient.getBlobUrl()).sasToken(sas)) + .buildAsyncClient() + .getBlockBlobAsyncClient(); + + return client.getPropertiesWithResponse(null); + }); + + StepVerifier.create(response).verifyErrorSatisfies(e -> { + assertExceptionStatusCodeAndMessage(e, 403, BlobErrorCode.AUTHENTICATION_FAILED); + }); + }); + } + @SuppressWarnings("deprecation") @Test public void blobSasSnapshot() { @@ -308,6 +379,71 @@ public void containerSasUserDelegation() { }); } + // RBAC replication lag + @Test + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") + public void containerSasUserDelegationDelegatedObjectId() { + liveTestScenarioWithRetry(() -> { + BlobContainerSasPermission permissions = new BlobContainerSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + + Flux response = getUserDelegationInfo().flatMapMany(key -> { + String sas = ccAsync.generateUserDelegationSas(sasValues, key); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. + BlobContainerAsyncClient client + = instrument(new BlobContainerClientBuilder().endpoint(ccAsync.getBlobContainerUrl()) + .sasToken(sas) + .credential(tokenCredential)).buildAsyncClient(); + + return client.listBlobs(); + }); + + StepVerifier.create(response).expectNextCount(1).verifyComplete(); + }); + } + + // RBAC replication lag + @Test + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") + public void containerSasUserDelegationDelegatedObjectIdFail() { + liveTestScenarioWithRetry(() -> { + BlobContainerSasPermission permissions = new BlobContainerSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + + Flux response = getUserDelegationInfo().flatMapMany(key -> { + String sas = ccAsync.generateUserDelegationSas(sasValues, key); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. Token credential is not provided here, so the request should fail. + BlobContainerAsyncClient client + = instrument(new BlobContainerClientBuilder().endpoint(ccAsync.getBlobContainerUrl()).sasToken(sas)) + .buildAsyncClient(); + + return client.listBlobs(); + }); + + StepVerifier.create(response).verifyErrorSatisfies(e -> { + assertExceptionStatusCodeAndMessage(e, 403, BlobErrorCode.AUTHENTICATION_FAILED); + }); + }); + } + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2019-12-12") @Test public void blobSasTags() { diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java index a8735887ab8c..0effdc5cbed7 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java @@ -3,13 +3,12 @@ package com.azure.storage.blob; -import com.azure.core.credential.AccessToken; import com.azure.core.credential.AzureSasCredential; import com.azure.core.credential.TokenCredential; -import com.azure.core.credential.TokenRequestContext; import com.azure.core.http.rest.Response; import com.azure.core.util.Context; import com.azure.storage.blob.implementation.util.BlobSasImplUtil; +import com.azure.storage.blob.models.BlobErrorCode; import com.azure.storage.blob.models.BlobProperties; import com.azure.storage.blob.models.BlobStorageException; import com.azure.storage.blob.models.UserDelegationKey; @@ -47,11 +46,9 @@ import java.time.ZoneOffset; import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Stream; -import static java.util.Base64.getUrlDecoder; +import static com.azure.storage.common.test.shared.StorageCommonTestUtils.getOidFromToken; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -213,6 +210,34 @@ public void blobSasUserDelegationDelegatedObjectId() { }); } + // RBAC replication lag + @Test + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") + public void blobSasUserDelegationDelegatedObjectIdFail() { + liveTestScenarioWithRetry(() -> { + BlobSasPermission permissions = new BlobSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + String sas = sasClient.generateUserDelegationSas(sasValues, getUserDelegationInfo()); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. Token credential is not provided here, so the request should fail. + BlockBlobClient client + = instrument(new BlobClientBuilder().endpoint(sasClient.getBlobUrl()).sasToken(sas)).buildClient() + .getBlockBlobClient(); + + BlobStorageException e = assertThrows(BlobStorageException.class, + () -> client.getPropertiesWithResponse(null, null, Context.NONE)); + assertExceptionStatusCodeAndMessage(e, 403, BlobErrorCode.AUTHENTICATION_FAILED); + }); + } + @SuppressWarnings("deprecation") @Test public void blobSasSnapshot() { @@ -325,18 +350,33 @@ public void containerSasUserDelegationDelegatedObjectId() { }); } - private String getOidFromToken(TokenCredential credential) { - AccessToken accessToken - = credential.getTokenSync(new TokenRequestContext().addScopes("https://storage.azure.com/.default")); - String[] chunks = accessToken.getToken().split("\\."); - String payload = new String(getUrlDecoder().decode(chunks[1])); + // RBAC replication lag + @Test + @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") + public void containerSasUserDelegationDelegatedObjectIdFail() { + liveTestScenarioWithRetry(() -> { + + BlobContainerSasPermission permissions = new BlobContainerSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); - Pattern pattern = Pattern.compile("\"oid\":\"(.*?)\""); - Matcher matcher = pattern.matcher(payload); - if (matcher.find()) { - return matcher.group(1); - } - throw new RuntimeException("Could not find oid in token"); + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + String sas = cc.generateUserDelegationSas(sasValues, getUserDelegationInfo()); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. Token credential is not provided here, so the request should fail. + BlobContainerClient client + = instrument(new BlobContainerClientBuilder().endpoint(cc.getBlobContainerUrl()).sasToken(sas)) + .buildClient(); + + BlobStorageException e + = assertThrows(BlobStorageException.class, () -> client.listBlobs().iterator().hasNext()); + assertExceptionStatusCodeAndMessage(e, 403, BlobErrorCode.AUTHENTICATION_FAILED); + }); } @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2019-12-12") diff --git a/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/StorageCommonTestUtils.java b/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/StorageCommonTestUtils.java index a57842ce582b..11137281e403 100644 --- a/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/StorageCommonTestUtils.java +++ b/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/StorageCommonTestUtils.java @@ -3,7 +3,9 @@ package com.azure.storage.common.test.shared; import com.azure.core.client.traits.HttpTrait; +import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRequestContext; import com.azure.core.http.HttpClient; import com.azure.core.http.netty.NettyAsyncHttpClientProvider; import com.azure.core.http.okhttp.OkHttpAsyncClientProvider; @@ -43,8 +45,11 @@ import java.util.Random; import java.util.UUID; import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.zip.CRC32; +import static java.util.Base64.getUrlDecoder; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -389,4 +394,24 @@ public static TokenCredential getTokenCredential(InterceptorManager interceptorM return builder.build(); } } + + /** + * Extracts the OID (Object ID) from a token. + * + * @param credential The TokenCredential to extract the OID from. + * @return The OID extracted from the token. + */ + public static String getOidFromToken(TokenCredential credential) { + AccessToken accessToken + = credential.getTokenSync(new TokenRequestContext().addScopes("https://storage.azure.com/.default")); + String[] chunks = accessToken.getToken().split("\\."); + String payload = new String(getUrlDecoder().decode(chunks[1])); + + Pattern pattern = Pattern.compile("\"oid\":\"(.*?)\""); + Matcher matcher = pattern.matcher(payload); + if (matcher.find()) { + return matcher.group(1); + } + throw new RuntimeException("Could not find oid in token"); + } } From 5b4bbcc5b2f50cb71b3c55e83d327ef6c80a867d Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 6 Aug 2025 10:53:15 -0700 Subject: [PATCH 03/14] re-adding recordings after pull --- sdk/storage/azure-storage-blob/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure-storage-blob/assets.json b/sdk/storage/azure-storage-blob/assets.json index 656c2fe951da..29161f92f9a1 100644 --- a/sdk/storage/azure-storage-blob/assets.json +++ b/sdk/storage/azure-storage-blob/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/storage/azure-storage-blob", - "Tag": "java/storage/azure-storage-blob_098e26235c" + "Tag": "java/storage/azure-storage-blob_692cef5d9c" } From db607b2b3ed7eec3298ebd239ccc734881a0c96a Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 6 Aug 2025 11:25:20 -0700 Subject: [PATCH 04/14] fixing string to sign test --- sdk/storage/azure-storage-blob/assets.json | 2 +- .../storage/blob/SasAsyncClientTests.java | 52 +++++++++++-------- .../azure/storage/blob/SasClientTests.java | 38 +++++++------- .../common/sas/CommonSasQueryParameters.java | 2 - 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/sdk/storage/azure-storage-blob/assets.json b/sdk/storage/azure-storage-blob/assets.json index 29161f92f9a1..01d8f91508ee 100644 --- a/sdk/storage/azure-storage-blob/assets.json +++ b/sdk/storage/azure-storage-blob/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/storage/azure-storage-blob", - "Tag": "java/storage/azure-storage-blob_692cef5d9c" + "Tag": "java/storage/azure-storage-blob_26d22d7fb8" } diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java index d095ed2c28f7..0f6d42afcec7 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java @@ -1149,7 +1149,7 @@ public void blobSasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTim OffsetDateTime keyStart, OffsetDateTime keyExpiry, String keyService, String keyVersion, String keyValue, SasIpRange ipRange, SasProtocol protocol, String snapId, String cacheControl, String disposition, String encoding, String language, String type, String versionId, String saoid, String cid, - String encryptionScope, String expectedStringToSign) { + String encryptionScope, String delegatedUserObjectId, String expectedStringToSign) { OffsetDateTime e = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); BlobSasPermission p = new BlobSasPermission().setReadPermission(true); BlobServiceSasSignatureValues v = new BlobServiceSasSignatureValues(e, p); @@ -1171,7 +1171,8 @@ public void blobSasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTim .setContentLanguage(language) .setContentType(type) .setPreauthorizedAgentObjectId(saoid) - .setCorrelationId(cid); + .setCorrelationId(cid) + .setDelegatedUserObjectId(delegatedUserObjectId); UserDelegationKey key = new UserDelegationKey().setSignedObjectId(keyOid) .setSignedTenantId(keyTid) @@ -1198,7 +1199,7 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup return Stream.of( Arguments.of(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, null, null, + null, null, null, null, "r\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1209,7 +1210,7 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, "11111111-1111-1111-1111-111111111111", null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, null, null, + null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1217,7 +1218,7 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, "22222222-2222-2222-2222-222222222222", null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, null, null, + null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1225,7 +1226,7 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC), null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, - null, null, null, null, null, + null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1233,14 +1234,14 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC), null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, - null, null, null, null, null, + null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n2018-01-01T00:00:00Z\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, "b", null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1248,96 +1249,103 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, "2018-06-17", "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, null, null, + null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n2018-06-17\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", - new SasIpRange(), null, null, null, null, null, null, null, null, null, null, null, + new SasIpRange(), null, null, null, null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\nip\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - SasProtocol.HTTPS_ONLY, null, null, null, null, null, null, null, null, null, null, + SasProtocol.HTTPS_ONLY, null, null, null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n" + SasProtocol.HTTPS_ONLY + "\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, "snapId", null, null, null, null, null, null, null, null, null, + null, "snapId", null, null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nbs\nsnapId\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, "control", null, null, null, null, null, null, null, null, + null, null, "control", null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\ncontrol\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, "disposition", null, null, null, null, null, null, null, + null, null, null, "disposition", null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\ndisposition\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, "encoding", null, null, null, null, null, null, + null, null, null, null, "encoding", null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\nencoding\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, "language", null, null, null, null, null, + null, null, null, null, null, "language", null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\nlanguage\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, "type", null, null, null, null, + null, null, null, null, null, null, "type", null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\ntype"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, "versionId", null, null, null, + null, null, null, null, null, null, null, "versionId", null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nbv\nversionId\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, "saoid", null, null, + null, null, null, null, null, null, null, null, "saoid", null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\nsaoid\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, null, "cid", null, + null, null, null, null, null, null, null, null, null, "cid", null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\ncid\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, null, null, "encryptionScope", + null, null, null, null, null, null, null, null, null, null, "encryptionScope", null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\nencryptionScope\n\n\n\n\n")); + + "\nb\n\nencryptionScope\n\n\n\n\n"), + Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, + null, null, null, null, null, null, null, null, null, null, null, "delegatedOid", + "r\n\n" + + Constants.ISO_8601_UTC_DATE_FORMATTER + .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\ndelegatedOid\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n")); } @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2020-12-06") diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java index 0effdc5cbed7..2ba5055a30d1 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java @@ -1109,7 +1109,7 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup return Stream.of( Arguments.of(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, null, null, + null, null, null, null, "r\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1120,7 +1120,7 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, "11111111-1111-1111-1111-111111111111", null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, null, null, + null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1128,7 +1128,7 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, "22222222-2222-2222-2222-222222222222", null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, null, null, + null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1136,7 +1136,7 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC), null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, - null, null, null, null, null, + null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1144,14 +1144,14 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC), null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, - null, null, null, null, null, + null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n2018-01-01T00:00:00Z\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, "b", null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1159,91 +1159,91 @@ private static Stream blobSasImplUtilStringToSignUserDelegationKeySup + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, "2018-06-17", "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, null, null, + null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n2018-06-17\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", - new SasIpRange(), null, null, null, null, null, null, null, null, null, null, null, + new SasIpRange(), null, null, null, null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\nip\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - SasProtocol.HTTPS_ONLY, null, null, null, null, null, null, null, null, null, null, + SasProtocol.HTTPS_ONLY, null, null, null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n" + SasProtocol.HTTPS_ONLY + "\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, "snapId", null, null, null, null, null, null, null, null, null, + null, "snapId", null, null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nbs\nsnapId\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, "control", null, null, null, null, null, null, null, null, + null, null, "control", null, null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\ncontrol\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, "disposition", null, null, null, null, null, null, null, + null, null, null, "disposition", null, null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\ndisposition\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, "encoding", null, null, null, null, null, null, + null, null, null, null, "encoding", null, null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\nencoding\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, "language", null, null, null, null, null, + null, null, null, null, null, "language", null, null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\nlanguage\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, "type", null, null, null, null, + null, null, null, null, null, null, "type", null, null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\ntype"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, "versionId", null, null, null, + null, null, null, null, null, null, null, "versionId", null, null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nbv\nversionId\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, "saoid", null, null, + null, null, null, null, null, null, null, null, "saoid", null, null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\nsaoid\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, null, "cid", null, + null, null, null, null, null, null, null, null, null, "cid", null, null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/containerName/blobName\n\n\n\n\n\n\n\n\ncid\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, null, null, "encryptionScope", + null, null, null, null, null, null, null, null, null, null, "encryptionScope", null, "r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) diff --git a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/sas/CommonSasQueryParameters.java b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/sas/CommonSasQueryParameters.java index 25d2e0ec2404..f4a526e50212 100644 --- a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/sas/CommonSasQueryParameters.java +++ b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/sas/CommonSasQueryParameters.java @@ -12,8 +12,6 @@ import java.util.Map; import java.util.function.Function; -import static com.azure.storage.common.implementation.SasImplUtils.tryAppendQueryParameter; - /** * Represents the components that make up an Azure Storage SAS' query parameters. This type is not constructed directly * by the user; it is only generated by the URLParts type. NOTE: Instances of this class are immutable to ensure thread From deb2696efad1c76b894753f6460e7ec860a51253 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 6 Aug 2025 11:39:27 -0700 Subject: [PATCH 05/14] forgot that token can't be recorded --- .../java/com/azure/storage/blob/SasAsyncClientTests.java | 5 +++++ .../src/test/java/com/azure/storage/blob/SasClientTests.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java index 0f6d42afcec7..86b9c91304fc 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasAsyncClientTests.java @@ -34,6 +34,7 @@ import com.azure.storage.common.sas.SasIpRange; import com.azure.storage.common.sas.SasProtocol; import com.azure.storage.common.test.shared.StorageCommonTestUtils; +import com.azure.storage.common.test.shared.extensions.LiveOnly; import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -203,6 +204,7 @@ public void blobSasUserDelegation() { // RBAC replication lag @Test + @LiveOnly @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") public void blobSasUserDelegationDelegatedObjectId() { liveTestScenarioWithRetry(() -> { @@ -235,6 +237,7 @@ public void blobSasUserDelegationDelegatedObjectId() { // RBAC replication lag @Test + @LiveOnly @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") public void blobSasUserDelegationDelegatedObjectIdFail() { liveTestScenarioWithRetry(() -> { @@ -381,6 +384,7 @@ public void containerSasUserDelegation() { // RBAC replication lag @Test + @LiveOnly @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") public void containerSasUserDelegationDelegatedObjectId() { liveTestScenarioWithRetry(() -> { @@ -413,6 +417,7 @@ public void containerSasUserDelegationDelegatedObjectId() { // RBAC replication lag @Test + @LiveOnly @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") public void containerSasUserDelegationDelegatedObjectIdFail() { liveTestScenarioWithRetry(() -> { diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java index 2ba5055a30d1..6e6a0f25d112 100644 --- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java @@ -30,6 +30,7 @@ import com.azure.storage.common.sas.SasIpRange; import com.azure.storage.common.sas.SasProtocol; import com.azure.storage.common.test.shared.StorageCommonTestUtils; +import com.azure.storage.common.test.shared.extensions.LiveOnly; import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -184,6 +185,7 @@ public void blobSasUserDelegation() { // RBAC replication lag @Test + @LiveOnly @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") public void blobSasUserDelegationDelegatedObjectId() { liveTestScenarioWithRetry(() -> { @@ -212,6 +214,7 @@ public void blobSasUserDelegationDelegatedObjectId() { // RBAC replication lag @Test + @LiveOnly @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") public void blobSasUserDelegationDelegatedObjectIdFail() { liveTestScenarioWithRetry(() -> { @@ -326,6 +329,7 @@ public void containerSasUserDelegation() { // RBAC replication lag @Test + @LiveOnly @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") public void containerSasUserDelegationDelegatedObjectId() { liveTestScenarioWithRetry(() -> { @@ -352,6 +356,7 @@ public void containerSasUserDelegationDelegatedObjectId() { // RBAC replication lag @Test + @LiveOnly @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-02-06") public void containerSasUserDelegationDelegatedObjectIdFail() { liveTestScenarioWithRetry(() -> { From a353c02fd0c4427a9159b712b3fd721587f8da22 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 6 Aug 2025 12:54:23 -0700 Subject: [PATCH 06/14] initial implementation --- .../azure/storage/queue/QueueAsyncClient.java | 36 +++ .../com/azure/storage/queue/QueueClient.java | 35 ++ .../queue/QueueServiceAsyncClient.java | 58 ++++ .../storage/queue/QueueServiceClient.java | 53 +++ .../queue/implementation/ServicesImpl.java | 273 ++++++++++++++++ .../ServicesGetUserDelegationKeyHeaders.java | 157 +++++++++ .../implementation/util/QueueSasImplUtil.java | 77 ++++- .../azure/storage/queue/models/KeyInfo.java | 144 +++++++++ .../queue/models/UserDelegationKey.java | 306 ++++++++++++++++++ .../sas/QueueServiceSasSignatureValues.java | 35 +- .../azure-storage-queue/swagger/README.md | 4 +- 11 files changed, 1160 insertions(+), 18 deletions(-) create mode 100644 sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/models/ServicesGetUserDelegationKeyHeaders.java create mode 100644 sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/KeyInfo.java create mode 100644 sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/UserDelegationKey.java diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java index 6df171fedff7..6e78ed63b4a5 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java @@ -37,12 +37,14 @@ import com.azure.storage.queue.models.QueueStorageException; import com.azure.storage.queue.models.SendMessageResult; import com.azure.storage.queue.models.UpdateMessageResult; +import com.azure.storage.queue.models.UserDelegationKey; import com.azure.storage.queue.sas.QueueServiceSasSignatureValues; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import java.time.Duration; +import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.List; @@ -1516,4 +1518,38 @@ public String generateSas(QueueServiceSasSignatureValues queueServiceSasSignatur return new QueueSasImplUtil(queueServiceSasSignatureValues, getQueueName()) .generateSas(SasImplUtils.extractSharedKeyCredential(getHttpPipeline()), stringToSignHandler, context); } + + /** + * Generates a user delegation SAS for the queue using the specified {@link QueueServiceSasSignatureValues}. + *

See {@link QueueServiceSasSignatureValues} for more information on how to construct a user delegation SAS.

+ * + * @param queueServiceSasSignatureValues {@link QueueServiceSasSignatureValues} + * @param userDelegationKey A {@link UserDelegationKey} object used to sign the SAS values. + * @param context Additional context that is passed through the code when generating a SAS. + * + * @return A {@code String} representing the SAS query parameters. + */ + public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServiceSasSignatureValues, + UserDelegationKey userDelegationKey, Context context) { + return generateUserDelegationSas(queueServiceSasSignatureValues, userDelegationKey, null, context); + } + + /** + * Generates a user delegation SAS for the queue using the specified {@link QueueServiceSasSignatureValues}. + *

See {@link QueueServiceSasSignatureValues} for more information on how to construct a user delegation SAS.

+ * + * @param queueServiceSasSignatureValues {@link QueueServiceSasSignatureValues} + * @param userDelegationKey A {@link UserDelegationKey} object used to sign the SAS values. + * @param stringToSignHandler For debugging purposes only. Returns the string to sign that was used to generate the + * signature. + * @param context Additional context that is passed through the code when generating a SAS. + * + * @return A {@code String} representing the SAS query parameters. + */ + public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServiceSasSignatureValues, + UserDelegationKey userDelegationKey, Consumer stringToSignHandler, + Context context) { + return new QueueSasImplUtil(queueServiceSasSignatureValues, getQueueName()).generateUserDelegationSas(userDelegationKey, accountName, + stringToSignHandler, context); + } } diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueClient.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueClient.java index 85d1994b6412..26cd7d5c2898 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueClient.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueClient.java @@ -32,6 +32,7 @@ import com.azure.storage.queue.implementation.models.QueuesGetAccessPolicyHeaders; import com.azure.storage.queue.implementation.models.QueuesGetPropertiesHeaders; import com.azure.storage.queue.implementation.models.SendMessageResultWrapper; +import com.azure.storage.queue.models.UserDelegationKey; import com.azure.storage.queue.implementation.util.ModelHelper; import com.azure.storage.queue.implementation.util.QueueSasImplUtil; import com.azure.storage.queue.models.PeekedMessageItem; @@ -1459,4 +1460,38 @@ public String generateSas(QueueServiceSasSignatureValues queueServiceSasSignatur return new QueueSasImplUtil(queueServiceSasSignatureValues, getQueueName()) .generateSas(SasImplUtils.extractSharedKeyCredential(getHttpPipeline()), stringToSignHandler, context); } + + /** + * Generates a user delegation SAS for the queue using the specified {@link QueueServiceSasSignatureValues}. + *

See {@link QueueServiceSasSignatureValues} for more information on how to construct a user delegation SAS.

+ * + * @param queueServiceSasSignatureValues {@link QueueServiceSasSignatureValues} + * @param userDelegationKey A {@link UserDelegationKey} object used to sign the SAS values. + * @param context Additional context that is passed through the code when generating a SAS. + * + * @return A {@code String} representing the SAS query parameters. + */ + public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServiceSasSignatureValues, + UserDelegationKey userDelegationKey, Context context) { + return generateUserDelegationSas(queueServiceSasSignatureValues, userDelegationKey, null, context); + } + + /** + * Generates a user delegation SAS for the queue using the specified {@link QueueServiceSasSignatureValues}. + *

See {@link QueueServiceSasSignatureValues} for more information on how to construct a user delegation SAS.

+ * + * @param queueServiceSasSignatureValues {@link QueueServiceSasSignatureValues} + * @param userDelegationKey A {@link UserDelegationKey} object used to sign the SAS values. + * @param stringToSignHandler For debugging purposes only. Returns the string to sign that was used to generate the + * signature. + * @param context Additional context that is passed through the code when generating a SAS. + * + * @return A {@code String} representing the SAS query parameters. + */ + public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServiceSasSignatureValues, + UserDelegationKey userDelegationKey, Consumer stringToSignHandler, + Context context) { + return new QueueSasImplUtil(queueServiceSasSignatureValues, getQueueName()) + .generateUserDelegationSas(userDelegationKey, accountName, stringToSignHandler, context); + } } diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceAsyncClient.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceAsyncClient.java index ce58fb71758f..92979bebaf0d 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceAsyncClient.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceAsyncClient.java @@ -5,6 +5,7 @@ import com.azure.core.annotation.ReturnType; import com.azure.core.annotation.ServiceClient; import com.azure.core.annotation.ServiceMethod; +import com.azure.core.credential.TokenCredential; import com.azure.core.http.HttpPipeline; import com.azure.core.http.rest.PagedFlux; import com.azure.core.http.rest.PagedResponse; @@ -15,10 +16,12 @@ import com.azure.core.util.logging.ClientLogger; import com.azure.storage.common.StorageSharedKeyCredential; import com.azure.storage.common.implementation.AccountSasImplUtil; +import com.azure.storage.common.implementation.Constants; import com.azure.storage.common.implementation.SasImplUtils; import com.azure.storage.common.implementation.StorageImplUtils; import com.azure.storage.common.sas.AccountSasSignatureValues; import com.azure.storage.queue.implementation.AzureQueueStorageImpl; +import com.azure.storage.queue.models.KeyInfo; import com.azure.storage.queue.models.QueueCorsRule; import com.azure.storage.queue.models.QueueItem; import com.azure.storage.queue.models.QueueMessageDecodingError; @@ -26,9 +29,11 @@ import com.azure.storage.queue.models.QueueServiceStatistics; import com.azure.storage.queue.models.QueueStorageException; import com.azure.storage.queue.models.QueuesSegmentOptions; +import com.azure.storage.queue.models.UserDelegationKey; import reactor.core.publisher.Mono; import java.time.Duration; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -709,4 +714,57 @@ public String generateAccountSas(AccountSasSignatureValues accountSasSignatureVa return new AccountSasImplUtil(accountSasSignatureValues, null) .generateSas(SasImplUtils.extractSharedKeyCredential(getHttpPipeline()), stringToSignHandler, context); } + + /** + * Gets a user delegation key for use with this account's queue storage. Note: This method call is only valid when + * using {@link TokenCredential} in this object's {@link HttpPipeline}. + * + * @param start Start time for the key's validity. Null indicates immediate start. + * @param expiry Expiration of the key's validity. + * @return A {@link Mono} containing the user delegation key. + * @throws IllegalArgumentException If {@code start} isn't null and is after {@code expiry}. + * @throws NullPointerException If {@code expiry} is null. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { + return getUserDelegationKeyWithResponse(start, expiry).flatMap(FluxUtil::toMono); + } + + /** + * Gets a user delegation key for use with this account's queue storage. Note: This method call is only valid when + * using {@link TokenCredential} in this object's {@link HttpPipeline}. + * + * @param start Start time for the key's validity. Null indicates immediate start. + * @param expiry Expiration of the key's validity. + * @return A {@link Mono} containing a {@link Response} whose {@link Response#getValue() value} containing the user + * delegation key. + * @throws IllegalArgumentException If {@code start} isn't null and is after {@code expiry}. + * @throws NullPointerException If {@code expiry} is null. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> getUserDelegationKeyWithResponse(OffsetDateTime start, + OffsetDateTime expiry) { + try { + return withContext(context -> getUserDelegationKeyWithResponse(start, expiry, context)); + } catch (RuntimeException ex) { + return monoError(LOGGER, ex); + } + } + + Mono> getUserDelegationKeyWithResponse(OffsetDateTime start, OffsetDateTime expiry, + Context context) { + StorageImplUtils.assertNotNull("expiry", expiry); + if (start != null && !start.isBefore(expiry)) { + throw LOGGER.logExceptionAsError( + new IllegalArgumentException("`start` must be null or a datetime before `expiry`.")); + } + context = context == null ? Context.NONE : context; + + return client.getServices() + .getUserDelegationKeyWithResponseAsync( + new KeyInfo().setStart(start == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(start)) + .setExpiry(Constants.ISO_8601_UTC_DATE_FORMATTER.format(expiry)), + null, null, context) + .map(rb -> new SimpleResponse<>(rb, rb.getValue())); + } } diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceClient.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceClient.java index c51516b84116..19ebf73272c0 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceClient.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceClient.java @@ -5,6 +5,7 @@ import com.azure.core.annotation.ReturnType; import com.azure.core.annotation.ServiceClient; import com.azure.core.annotation.ServiceMethod; +import com.azure.core.credential.TokenCredential; import com.azure.core.http.HttpPipeline; import com.azure.core.http.rest.PagedIterable; import com.azure.core.http.rest.PagedResponse; @@ -15,10 +16,14 @@ import com.azure.core.util.logging.ClientLogger; import com.azure.storage.common.StorageSharedKeyCredential; import com.azure.storage.common.implementation.AccountSasImplUtil; +import com.azure.storage.common.implementation.Constants; import com.azure.storage.common.implementation.SasImplUtils; +import com.azure.storage.common.implementation.StorageImplUtils; import com.azure.storage.common.sas.AccountSasSignatureValues; import com.azure.storage.queue.implementation.AzureQueueStorageImpl; +import com.azure.storage.queue.models.KeyInfo; import com.azure.storage.queue.implementation.models.ServicesGetStatisticsHeaders; +import com.azure.storage.queue.implementation.models.ServicesGetUserDelegationKeyHeaders; import com.azure.storage.queue.models.QueueCorsRule; import com.azure.storage.queue.models.QueueItem; import com.azure.storage.queue.models.QueueMessageDecodingError; @@ -26,18 +31,22 @@ import com.azure.storage.queue.models.QueueServiceStatistics; import com.azure.storage.queue.models.QueueStorageException; import com.azure.storage.queue.models.QueuesSegmentOptions; +import com.azure.storage.queue.models.UserDelegationKey; import reactor.core.publisher.Mono; import java.time.Duration; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.Callable; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import static com.azure.storage.common.implementation.StorageImplUtils.sendRequest; import static com.azure.storage.common.implementation.StorageImplUtils.submitThreadPool; /** @@ -693,4 +702,48 @@ public String generateAccountSas(AccountSasSignatureValues accountSasSignatureVa return new AccountSasImplUtil(accountSasSignatureValues, null) .generateSas(SasImplUtils.extractSharedKeyCredential(getHttpPipeline()), stringToSignHandler, context); } + + /** + * Gets a user delegation key for use with this account's queue storage. Note: This method call is only valid when + * using {@link TokenCredential} in this object's {@link HttpPipeline}. + * + * @param start Start time for the key's validity. Null indicates immediate start. + * @param expiry Expiration of the key's validity. + * @return The user delegation key. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public UserDelegationKey getUserDelegationKey(OffsetDateTime start, OffsetDateTime expiry) { + return getUserDelegationKeyWithResponse(start, expiry, null, Context.NONE).getValue(); + } + + /** + * Gets a user delegation key for use with this account's queue storage. Note: This method call is only valid when + * using {@link TokenCredential} in this object's {@link HttpPipeline}. + * + * @param start Start time for the key's validity. Null indicates immediate start. + * @param expiry Expiration of the key's validity. + * @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised. + * @param context Additional context that is passed through the Http pipeline during the service call. + * @return A {@link Response} whose {@link Response#getValue() value} contains the user delegation key. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Response getUserDelegationKeyWithResponse(OffsetDateTime start, OffsetDateTime expiry, + Duration timeout, Context context) { + StorageImplUtils.assertNotNull("expiry", expiry); + if (start != null && !start.isBefore(expiry)) { + throw LOGGER.logExceptionAsError( + new IllegalArgumentException("`start` must be null or a datetime before `expiry`.")); + } + Context finalContext = context == null ? Context.NONE : context; + Callable> operation + = () -> this.azureQueueStorage.getServices() + .getUserDelegationKeyWithResponse( + new KeyInfo().setStart(start == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(start)) + .setExpiry(Constants.ISO_8601_UTC_DATE_FORMATTER.format(expiry)), + null, null, finalContext); + ResponseBase response + = sendRequest(operation, timeout, QueueStorageException.class); + return new SimpleResponse<>(response, response.getValue()); + } + } diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/ServicesImpl.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/ServicesImpl.java index 3e066282d0ef..b5224b043ffd 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/ServicesImpl.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/ServicesImpl.java @@ -10,6 +10,7 @@ import com.azure.core.annotation.Host; import com.azure.core.annotation.HostParam; import com.azure.core.annotation.PathParam; +import com.azure.core.annotation.Post; import com.azure.core.annotation.Put; import com.azure.core.annotation.QueryParam; import com.azure.core.annotation.ReturnType; @@ -29,13 +30,16 @@ import com.azure.storage.queue.implementation.models.QueueStorageExceptionInternal; import com.azure.storage.queue.implementation.models.ServicesGetPropertiesHeaders; import com.azure.storage.queue.implementation.models.ServicesGetStatisticsHeaders; +import com.azure.storage.queue.implementation.models.ServicesGetUserDelegationKeyHeaders; import com.azure.storage.queue.implementation.models.ServicesListQueuesSegmentHeaders; import com.azure.storage.queue.implementation.models.ServicesListQueuesSegmentNextHeaders; import com.azure.storage.queue.implementation.models.ServicesSetPropertiesHeaders; import com.azure.storage.queue.implementation.util.ModelHelper; +import com.azure.storage.queue.models.KeyInfo; import com.azure.storage.queue.models.QueueItem; import com.azure.storage.queue.models.QueueServiceProperties; import com.azure.storage.queue.models.QueueServiceStatistics; +import com.azure.storage.queue.models.UserDelegationKey; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -186,6 +190,42 @@ Response getStatisticsNoCustomHeadersSync(@HostParam("ur @HeaderParam("x-ms-client-request-id") String requestId, @HeaderParam("Accept") String accept, Context context); + @Post("/") + @ExpectedResponses({ 200 }) + @UnexpectedResponseExceptionType(QueueStorageExceptionInternal.class) + Mono> getUserDelegationKey( + @HostParam("url") String url, @QueryParam("restype") String restype, @QueryParam("comp") String comp, + @QueryParam("timeout") Integer timeout, @HeaderParam("x-ms-version") String version, + @HeaderParam("x-ms-client-request-id") String requestId, @BodyParam("application/xml") KeyInfo keyInfo, + @HeaderParam("Accept") String accept, Context context); + + @Post("/") + @ExpectedResponses({ 200 }) + @UnexpectedResponseExceptionType(QueueStorageExceptionInternal.class) + Mono> getUserDelegationKeyNoCustomHeaders(@HostParam("url") String url, + @QueryParam("restype") String restype, @QueryParam("comp") String comp, + @QueryParam("timeout") Integer timeout, @HeaderParam("x-ms-version") String version, + @HeaderParam("x-ms-client-request-id") String requestId, @BodyParam("application/xml") KeyInfo keyInfo, + @HeaderParam("Accept") String accept, Context context); + + @Post("/") + @ExpectedResponses({ 200 }) + @UnexpectedResponseExceptionType(QueueStorageExceptionInternal.class) + ResponseBase getUserDelegationKeySync( + @HostParam("url") String url, @QueryParam("restype") String restype, @QueryParam("comp") String comp, + @QueryParam("timeout") Integer timeout, @HeaderParam("x-ms-version") String version, + @HeaderParam("x-ms-client-request-id") String requestId, @BodyParam("application/xml") KeyInfo keyInfo, + @HeaderParam("Accept") String accept, Context context); + + @Post("/") + @ExpectedResponses({ 200 }) + @UnexpectedResponseExceptionType(QueueStorageExceptionInternal.class) + Response getUserDelegationKeyNoCustomHeadersSync(@HostParam("url") String url, + @QueryParam("restype") String restype, @QueryParam("comp") String comp, + @QueryParam("timeout") Integer timeout, @HeaderParam("x-ms-version") String version, + @HeaderParam("x-ms-client-request-id") String requestId, @BodyParam("application/xml") KeyInfo keyInfo, + @HeaderParam("Accept") String accept, Context context); + @Get("/") @ExpectedResponses({ 200 }) @UnexpectedResponseExceptionType(QueueStorageExceptionInternal.class) @@ -939,6 +979,239 @@ public Response getStatisticsNoCustomHeadersWithResponse } } + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key along with {@link ResponseBase} on successful completion of {@link Mono}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> + getUserDelegationKeyWithResponseAsync(KeyInfo keyInfo, Integer timeout, String requestId) { + return FluxUtil + .withContext(context -> getUserDelegationKeyWithResponseAsync(keyInfo, timeout, requestId, context)) + .onErrorMap(QueueStorageExceptionInternal.class, ModelHelper::mapToQueueStorageException); + } + + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @param context The context to associate with this operation. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key along with {@link ResponseBase} on successful completion of {@link Mono}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> + getUserDelegationKeyWithResponseAsync(KeyInfo keyInfo, Integer timeout, String requestId, Context context) { + final String restype = "service"; + final String comp = "userdelegationkey"; + final String accept = "application/xml"; + return service + .getUserDelegationKey(this.client.getUrl(), restype, comp, timeout, this.client.getVersion(), requestId, + keyInfo, accept, context) + .onErrorMap(QueueStorageExceptionInternal.class, ModelHelper::mapToQueueStorageException); + } + + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key on successful completion of {@link Mono}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono getUserDelegationKeyAsync(KeyInfo keyInfo, Integer timeout, String requestId) { + return getUserDelegationKeyWithResponseAsync(keyInfo, timeout, requestId) + .onErrorMap(QueueStorageExceptionInternal.class, ModelHelper::mapToQueueStorageException) + .flatMap(res -> Mono.justOrEmpty(res.getValue())); + } + + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @param context The context to associate with this operation. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key on successful completion of {@link Mono}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono getUserDelegationKeyAsync(KeyInfo keyInfo, Integer timeout, String requestId, + Context context) { + return getUserDelegationKeyWithResponseAsync(keyInfo, timeout, requestId, context) + .onErrorMap(QueueStorageExceptionInternal.class, ModelHelper::mapToQueueStorageException) + .flatMap(res -> Mono.justOrEmpty(res.getValue())); + } + + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key along with {@link Response} on successful completion of {@link Mono}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> getUserDelegationKeyNoCustomHeadersWithResponseAsync(KeyInfo keyInfo, + Integer timeout, String requestId) { + return FluxUtil + .withContext( + context -> getUserDelegationKeyNoCustomHeadersWithResponseAsync(keyInfo, timeout, requestId, context)) + .onErrorMap(QueueStorageExceptionInternal.class, ModelHelper::mapToQueueStorageException); + } + + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @param context The context to associate with this operation. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key along with {@link Response} on successful completion of {@link Mono}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> getUserDelegationKeyNoCustomHeadersWithResponseAsync(KeyInfo keyInfo, + Integer timeout, String requestId, Context context) { + final String restype = "service"; + final String comp = "userdelegationkey"; + final String accept = "application/xml"; + return service + .getUserDelegationKeyNoCustomHeaders(this.client.getUrl(), restype, comp, timeout, this.client.getVersion(), + requestId, keyInfo, accept, context) + .onErrorMap(QueueStorageExceptionInternal.class, ModelHelper::mapToQueueStorageException); + } + + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @param context The context to associate with this operation. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key along with {@link ResponseBase}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public ResponseBase + getUserDelegationKeyWithResponse(KeyInfo keyInfo, Integer timeout, String requestId, Context context) { + try { + final String restype = "service"; + final String comp = "userdelegationkey"; + final String accept = "application/xml"; + return service.getUserDelegationKeySync(this.client.getUrl(), restype, comp, timeout, + this.client.getVersion(), requestId, keyInfo, accept, context); + } catch (QueueStorageExceptionInternal internalException) { + throw ModelHelper.mapToQueueStorageException(internalException); + } + } + + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public UserDelegationKey getUserDelegationKey(KeyInfo keyInfo, Integer timeout, String requestId) { + try { + return getUserDelegationKeyWithResponse(keyInfo, timeout, requestId, Context.NONE).getValue(); + } catch (QueueStorageExceptionInternal internalException) { + throw ModelHelper.mapToQueueStorageException(internalException); + } + } + + /** + * Retrieves a user delegation key for the Queue service. This is only a valid operation when using bearer token + * authentication. + * + * @param keyInfo Key information. + * @param timeout The The timeout parameter is expressed in seconds. For more information, see <a + * href="https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-queue-service-operations>Setting + * Timeouts for Queue Service Operations.</a>. + * @param requestId Provides a client-generated, opaque value with a 1 KB character limit that is recorded in the + * analytics logs when storage analytics logging is enabled. + * @param context The context to associate with this operation. + * @throws IllegalArgumentException thrown if parameters fail the validation. + * @throws QueueStorageExceptionInternal thrown if the request is rejected by server. + * @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent. + * @return a user delegation key along with {@link Response}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Response getUserDelegationKeyNoCustomHeadersWithResponse(KeyInfo keyInfo, Integer timeout, + String requestId, Context context) { + try { + final String restype = "service"; + final String comp = "userdelegationkey"; + final String accept = "application/xml"; + return service.getUserDelegationKeyNoCustomHeadersSync(this.client.getUrl(), restype, comp, timeout, + this.client.getVersion(), requestId, keyInfo, accept, context); + } catch (QueueStorageExceptionInternal internalException) { + throw ModelHelper.mapToQueueStorageException(internalException); + } + } + /** * The List Queues Segment operation returns a list of the queues under the specified account. * diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/models/ServicesGetUserDelegationKeyHeaders.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/models/ServicesGetUserDelegationKeyHeaders.java new file mode 100644 index 000000000000..83c3124e9c4f --- /dev/null +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/models/ServicesGetUserDelegationKeyHeaders.java @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) AutoRest Code Generator. + +package com.azure.storage.queue.implementation.models; + +import com.azure.core.annotation.Fluent; +import com.azure.core.annotation.Generated; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpHeaders; +import com.azure.core.util.DateTimeRfc1123; +import java.time.OffsetDateTime; + +/** + * The ServicesGetUserDelegationKeyHeaders model. + */ +@Fluent +public final class ServicesGetUserDelegationKeyHeaders { + /* + * The x-ms-version property. + */ + @Generated + private String xMsVersion; + + /* + * The x-ms-request-id property. + */ + @Generated + private String xMsRequestId; + + /* + * The x-ms-client-request-id property. + */ + @Generated + private String xMsClientRequestId; + + /* + * The Date property. + */ + @Generated + private DateTimeRfc1123 date; + + private static final HttpHeaderName X_MS_VERSION = HttpHeaderName.fromString("x-ms-version"); + + // HttpHeaders containing the raw property values. + /** + * Creates an instance of ServicesGetUserDelegationKeyHeaders class. + * + * @param rawHeaders The raw HttpHeaders that will be used to create the property values. + */ + public ServicesGetUserDelegationKeyHeaders(HttpHeaders rawHeaders) { + this.xMsVersion = rawHeaders.getValue(X_MS_VERSION); + this.xMsRequestId = rawHeaders.getValue(HttpHeaderName.X_MS_REQUEST_ID); + this.xMsClientRequestId = rawHeaders.getValue(HttpHeaderName.X_MS_CLIENT_REQUEST_ID); + String date = rawHeaders.getValue(HttpHeaderName.DATE); + if (date != null) { + this.date = new DateTimeRfc1123(date); + } else { + this.date = null; + } + } + + /** + * Get the xMsVersion property: The x-ms-version property. + * + * @return the xMsVersion value. + */ + @Generated + public String getXMsVersion() { + return this.xMsVersion; + } + + /** + * Set the xMsVersion property: The x-ms-version property. + * + * @param xMsVersion the xMsVersion value to set. + * @return the ServicesGetUserDelegationKeyHeaders object itself. + */ + @Generated + public ServicesGetUserDelegationKeyHeaders setXMsVersion(String xMsVersion) { + this.xMsVersion = xMsVersion; + return this; + } + + /** + * Get the xMsRequestId property: The x-ms-request-id property. + * + * @return the xMsRequestId value. + */ + @Generated + public String getXMsRequestId() { + return this.xMsRequestId; + } + + /** + * Set the xMsRequestId property: The x-ms-request-id property. + * + * @param xMsRequestId the xMsRequestId value to set. + * @return the ServicesGetUserDelegationKeyHeaders object itself. + */ + @Generated + public ServicesGetUserDelegationKeyHeaders setXMsRequestId(String xMsRequestId) { + this.xMsRequestId = xMsRequestId; + return this; + } + + /** + * Get the xMsClientRequestId property: The x-ms-client-request-id property. + * + * @return the xMsClientRequestId value. + */ + @Generated + public String getXMsClientRequestId() { + return this.xMsClientRequestId; + } + + /** + * Set the xMsClientRequestId property: The x-ms-client-request-id property. + * + * @param xMsClientRequestId the xMsClientRequestId value to set. + * @return the ServicesGetUserDelegationKeyHeaders object itself. + */ + @Generated + public ServicesGetUserDelegationKeyHeaders setXMsClientRequestId(String xMsClientRequestId) { + this.xMsClientRequestId = xMsClientRequestId; + return this; + } + + /** + * Get the date property: The Date property. + * + * @return the date value. + */ + @Generated + public OffsetDateTime getDate() { + if (this.date == null) { + return null; + } + return this.date.getDateTime(); + } + + /** + * Set the date property: The Date property. + * + * @param date the date value to set. + * @return the ServicesGetUserDelegationKeyHeaders object itself. + */ + @Generated + public ServicesGetUserDelegationKeyHeaders setDate(OffsetDateTime date) { + if (date == null) { + this.date = null; + } else { + this.date = new DateTimeRfc1123(date); + } + return this; + } +} diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java index d79c87724d4e..d74530f1e37b 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java @@ -13,6 +13,7 @@ import com.azure.storage.common.sas.SasIpRange; import com.azure.storage.common.sas.SasProtocol; import com.azure.storage.queue.QueueServiceVersion; +import com.azure.storage.queue.models.UserDelegationKey; import com.azure.storage.queue.sas.QueueSasPermission; import com.azure.storage.queue.sas.QueueServiceSasSignatureValues; @@ -36,18 +37,13 @@ public class QueueSasImplUtil { .get(Constants.PROPERTY_AZURE_STORAGE_SAS_SERVICE_VERSION, QueueServiceVersion.getLatest().getVersion()); private SasProtocol protocol; - private OffsetDateTime startTime; - private OffsetDateTime expiryTime; - private String permissions; - private SasIpRange sasIpRange; - private String queueName; - private String identifier; + private String delegatedUserObjectId; /** * Creates a new {@link QueueSasImplUtil} with the specified parameters @@ -64,6 +60,7 @@ public QueueSasImplUtil(QueueServiceSasSignatureValues sasValues, String queueNa this.sasIpRange = sasValues.getSasIpRange(); this.queueName = queueName; this.identifier = sasValues.getIdentifier(); + this.delegatedUserObjectId = sasValues.getDelegatedUserObjectId(); } /** @@ -102,10 +99,40 @@ public String generateSas(StorageSharedKeyCredential storageSharedKeyCredentials stringToSignHandler.accept(stringToSign); } - return encode(signature); + return encode(null /* userDelegationKey */, signature); } - private String encode(String signature) { + /** + * Generates a Sas signed with a {@link UserDelegationKey} + * + * @param delegationKey {@link UserDelegationKey} + * @param accountName The account name + * @param stringToSignHandler For debugging purposes only. Returns the string to sign that was used to generate the + * signature. + * @param context Additional context that is passed through the code when generating a SAS. + * @return A String representing the Sas + */ + public String generateUserDelegationSas(UserDelegationKey delegationKey, String accountName, + Consumer stringToSignHandler, Context context) { + StorageImplUtils.assertNotNull("delegationKey", delegationKey); + StorageImplUtils.assertNotNull("accountName", accountName); + + ensureState(); + + // Signature is generated on the un-url-encoded values. + final String canonicalName = getCanonicalName(accountName); + final String stringToSign = stringToSign(delegationKey, canonicalName); + StorageImplUtils.logStringToSign(LOGGER, stringToSign, context); + String signature = StorageImplUtils.computeHMac256(delegationKey.getValue(), stringToSign); + + if (stringToSignHandler != null) { + stringToSignHandler.accept(stringToSign); + } + + return encode(delegationKey, signature); + } + + private String encode(UserDelegationKey userDelegationKey, String signature) { /* We should be url-encoding each key and each value, but because we know all the keys and values will encode to themselves, we cheat except for the signature value. @@ -120,6 +147,21 @@ private String encode(String signature) { formatQueryParameterDate(new TimeAndFormat(this.expiryTime, null))); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_IDENTIFIER, this.identifier); + if (userDelegationKey != null) { + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_OBJECT_ID, + userDelegationKey.getSignedOid()); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_TENANT_ID, + userDelegationKey.getSignedTid()); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_START, + formatQueryParameterDate(new TimeAndFormat(userDelegationKey.getSignedStart(), null))); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_EXPIRY, + formatQueryParameterDate(new TimeAndFormat(userDelegationKey.getSignedExpiry(), null))); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_SERVICE, + userDelegationKey.getSignedService()); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_VERSION, + userDelegationKey.getSignedVersion()); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_DELEGATED_USER_OBJECT_ID, this.delegatedUserObjectId); + } tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNATURE, signature); @@ -128,7 +170,7 @@ private String encode(String signature) { /** * Ensures that the builder's properties are in a consistent state. - + * 1. If there is no version, use latest. * 2. If there is no identifier set, ensure expiryTime and permissions are set. * 4. Reparse permissions depending on what the resource is. If it is an unrecognised resource, do nothing. @@ -169,4 +211,21 @@ private String stringToSign(String canonicalName) { this.protocol == null ? "" : protocol.toString(), VERSION == null ? "" : VERSION); } + private String stringToSign(final UserDelegationKey key, String canonicalName) { + return String.join("\n", this.permissions == null ? "" : this.permissions, + this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), + this.expiryTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), canonicalName, + key.getSignedOid() == null ? "" : key.getSignedOid(), + key.getSignedTid() == null ? "" : key.getSignedTid(), + key.getSignedStart() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedStart()), + key.getSignedExpiry() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedExpiry()), + key.getSignedService() == null ? "" : key.getSignedService(), + key.getSignedVersion() == null ? "" : key.getSignedVersion(), + null, // SignedKeyDelegatedUserTenantId, will be added in a future release. + this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId, + this.sasIpRange == null ? "" : this.sasIpRange.toString(), + this.protocol == null ? "" : this.protocol.toString(), + VERSION); + } + } diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/KeyInfo.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/KeyInfo.java new file mode 100644 index 000000000000..330812896c7a --- /dev/null +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/KeyInfo.java @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) AutoRest Code Generator. + +package com.azure.storage.queue.models; + +import com.azure.core.annotation.Fluent; +import com.azure.core.annotation.Generated; +import com.azure.xml.XmlReader; +import com.azure.xml.XmlSerializable; +import com.azure.xml.XmlToken; +import com.azure.xml.XmlWriter; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +/** + * Key information. + */ +@Fluent +public final class KeyInfo implements XmlSerializable { + /* + * The date-time the key is active in ISO 8601 UTC time + */ + @Generated + private String start; + + /* + * The date-time the key expires in ISO 8601 UTC time + */ + @Generated + private String expiry; + + /** + * Creates an instance of KeyInfo class. + */ + @Generated + public KeyInfo() { + } + + /** + * Get the start property: The date-time the key is active in ISO 8601 UTC time. + * + * @return the start value. + */ + @Generated + public String getStart() { + return this.start; + } + + /** + * Set the start property: The date-time the key is active in ISO 8601 UTC time. + * + * @param start the start value to set. + * @return the KeyInfo object itself. + */ + @Generated + public KeyInfo setStart(String start) { + this.start = start; + return this; + } + + /** + * Get the expiry property: The date-time the key expires in ISO 8601 UTC time. + * + * @return the expiry value. + */ + @Generated + public String getExpiry() { + return this.expiry; + } + + /** + * Set the expiry property: The date-time the key expires in ISO 8601 UTC time. + * + * @param expiry the expiry value to set. + * @return the KeyInfo object itself. + */ + @Generated + public KeyInfo setExpiry(String expiry) { + this.expiry = expiry; + return this; + } + + @Generated + @Override + public XmlWriter toXml(XmlWriter xmlWriter) throws XMLStreamException { + return toXml(xmlWriter, null); + } + + @Generated + @Override + public XmlWriter toXml(XmlWriter xmlWriter, String rootElementName) throws XMLStreamException { + rootElementName = rootElementName == null || rootElementName.isEmpty() ? "KeyInfo" : rootElementName; + xmlWriter.writeStartElement(rootElementName); + xmlWriter.writeStringElement("Start", this.start); + xmlWriter.writeStringElement("Expiry", this.expiry); + return xmlWriter.writeEndElement(); + } + + /** + * Reads an instance of KeyInfo from the XmlReader. + * + * @param xmlReader The XmlReader being read. + * @return An instance of KeyInfo if the XmlReader was pointing to an instance of it, or null if it was pointing to + * XML null. + * @throws XMLStreamException If an error occurs while reading the KeyInfo. + */ + @Generated + public static KeyInfo fromXml(XmlReader xmlReader) throws XMLStreamException { + return fromXml(xmlReader, null); + } + + /** + * Reads an instance of KeyInfo from the XmlReader. + * + * @param xmlReader The XmlReader being read. + * @param rootElementName Optional root element name to override the default defined by the model. Used to support + * cases where the model can deserialize from different root element names. + * @return An instance of KeyInfo if the XmlReader was pointing to an instance of it, or null if it was pointing to + * XML null. + * @throws XMLStreamException If an error occurs while reading the KeyInfo. + */ + @Generated + public static KeyInfo fromXml(XmlReader xmlReader, String rootElementName) throws XMLStreamException { + String finalRootElementName + = rootElementName == null || rootElementName.isEmpty() ? "KeyInfo" : rootElementName; + return xmlReader.readObject(finalRootElementName, reader -> { + KeyInfo deserializedKeyInfo = new KeyInfo(); + while (reader.nextElement() != XmlToken.END_ELEMENT) { + QName elementName = reader.getElementName(); + + if ("Start".equals(elementName.getLocalPart())) { + deserializedKeyInfo.start = reader.getStringElement(); + } else if ("Expiry".equals(elementName.getLocalPart())) { + deserializedKeyInfo.expiry = reader.getStringElement(); + } else { + reader.skipElement(); + } + } + + return deserializedKeyInfo; + }); + } +} diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/UserDelegationKey.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/UserDelegationKey.java new file mode 100644 index 000000000000..13dbb1e040b4 --- /dev/null +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/UserDelegationKey.java @@ -0,0 +1,306 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) AutoRest Code Generator. + +package com.azure.storage.queue.models; + +import com.azure.core.annotation.Fluent; +import com.azure.core.annotation.Generated; +import com.azure.core.util.CoreUtils; +import com.azure.xml.XmlReader; +import com.azure.xml.XmlSerializable; +import com.azure.xml.XmlToken; +import com.azure.xml.XmlWriter; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +/** + * A user delegation key. + */ +@Fluent +public final class UserDelegationKey implements XmlSerializable { + /* + * The Azure Active Directory object ID in GUID format. + */ + @Generated + private String signedOid; + + /* + * The Azure Active Directory tenant ID in GUID format + */ + @Generated + private String signedTid; + + /* + * The date-time the key is active + */ + @Generated + private OffsetDateTime signedStart; + + /* + * The date-time the key expires + */ + @Generated + private OffsetDateTime signedExpiry; + + /* + * Abbreviation of the Azure Storage service that accepts the key + */ + @Generated + private String signedService; + + /* + * The service version that created the key + */ + @Generated + private String signedVersion; + + /* + * The key as a base64 string + */ + @Generated + private String value; + + /** + * Creates an instance of UserDelegationKey class. + */ + @Generated + public UserDelegationKey() { + } + + /** + * Get the signedOid property: The Azure Active Directory object ID in GUID format. + * + * @return the signedOid value. + */ + @Generated + public String getSignedOid() { + return this.signedOid; + } + + /** + * Set the signedOid property: The Azure Active Directory object ID in GUID format. + * + * @param signedOid the signedOid value to set. + * @return the UserDelegationKey object itself. + */ + @Generated + public UserDelegationKey setSignedOid(String signedOid) { + this.signedOid = signedOid; + return this; + } + + /** + * Get the signedTid property: The Azure Active Directory tenant ID in GUID format. + * + * @return the signedTid value. + */ + @Generated + public String getSignedTid() { + return this.signedTid; + } + + /** + * Set the signedTid property: The Azure Active Directory tenant ID in GUID format. + * + * @param signedTid the signedTid value to set. + * @return the UserDelegationKey object itself. + */ + @Generated + public UserDelegationKey setSignedTid(String signedTid) { + this.signedTid = signedTid; + return this; + } + + /** + * Get the signedStart property: The date-time the key is active. + * + * @return the signedStart value. + */ + @Generated + public OffsetDateTime getSignedStart() { + return this.signedStart; + } + + /** + * Set the signedStart property: The date-time the key is active. + * + * @param signedStart the signedStart value to set. + * @return the UserDelegationKey object itself. + */ + @Generated + public UserDelegationKey setSignedStart(OffsetDateTime signedStart) { + this.signedStart = signedStart; + return this; + } + + /** + * Get the signedExpiry property: The date-time the key expires. + * + * @return the signedExpiry value. + */ + @Generated + public OffsetDateTime getSignedExpiry() { + return this.signedExpiry; + } + + /** + * Set the signedExpiry property: The date-time the key expires. + * + * @param signedExpiry the signedExpiry value to set. + * @return the UserDelegationKey object itself. + */ + @Generated + public UserDelegationKey setSignedExpiry(OffsetDateTime signedExpiry) { + this.signedExpiry = signedExpiry; + return this; + } + + /** + * Get the signedService property: Abbreviation of the Azure Storage service that accepts the key. + * + * @return the signedService value. + */ + @Generated + public String getSignedService() { + return this.signedService; + } + + /** + * Set the signedService property: Abbreviation of the Azure Storage service that accepts the key. + * + * @param signedService the signedService value to set. + * @return the UserDelegationKey object itself. + */ + @Generated + public UserDelegationKey setSignedService(String signedService) { + this.signedService = signedService; + return this; + } + + /** + * Get the signedVersion property: The service version that created the key. + * + * @return the signedVersion value. + */ + @Generated + public String getSignedVersion() { + return this.signedVersion; + } + + /** + * Set the signedVersion property: The service version that created the key. + * + * @param signedVersion the signedVersion value to set. + * @return the UserDelegationKey object itself. + */ + @Generated + public UserDelegationKey setSignedVersion(String signedVersion) { + this.signedVersion = signedVersion; + return this; + } + + /** + * Get the value property: The key as a base64 string. + * + * @return the value value. + */ + @Generated + public String getValue() { + return this.value; + } + + /** + * Set the value property: The key as a base64 string. + * + * @param value the value value to set. + * @return the UserDelegationKey object itself. + */ + @Generated + public UserDelegationKey setValue(String value) { + this.value = value; + return this; + } + + @Generated + @Override + public XmlWriter toXml(XmlWriter xmlWriter) throws XMLStreamException { + return toXml(xmlWriter, null); + } + + @Generated + @Override + public XmlWriter toXml(XmlWriter xmlWriter, String rootElementName) throws XMLStreamException { + rootElementName = rootElementName == null || rootElementName.isEmpty() ? "UserDelegationKey" : rootElementName; + xmlWriter.writeStartElement(rootElementName); + xmlWriter.writeStringElement("SignedOid", this.signedOid); + xmlWriter.writeStringElement("SignedTid", this.signedTid); + xmlWriter.writeStringElement("SignedStart", + this.signedStart == null ? null : DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(this.signedStart)); + xmlWriter.writeStringElement("SignedExpiry", + this.signedExpiry == null ? null : DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(this.signedExpiry)); + xmlWriter.writeStringElement("SignedService", this.signedService); + xmlWriter.writeStringElement("SignedVersion", this.signedVersion); + xmlWriter.writeStringElement("Value", this.value); + return xmlWriter.writeEndElement(); + } + + /** + * Reads an instance of UserDelegationKey from the XmlReader. + * + * @param xmlReader The XmlReader being read. + * @return An instance of UserDelegationKey if the XmlReader was pointing to an instance of it, or null if it was + * pointing to XML null. + * @throws XMLStreamException If an error occurs while reading the UserDelegationKey. + */ + @Generated + public static UserDelegationKey fromXml(XmlReader xmlReader) throws XMLStreamException { + return fromXml(xmlReader, null); + } + + /** + * Reads an instance of UserDelegationKey from the XmlReader. + * + * @param xmlReader The XmlReader being read. + * @param rootElementName Optional root element name to override the default defined by the model. Used to support + * cases where the model can deserialize from different root element names. + * @return An instance of UserDelegationKey if the XmlReader was pointing to an instance of it, or null if it was + * pointing to XML null. + * @throws XMLStreamException If an error occurs while reading the UserDelegationKey. + */ + @Generated + public static UserDelegationKey fromXml(XmlReader xmlReader, String rootElementName) throws XMLStreamException { + String finalRootElementName + = rootElementName == null || rootElementName.isEmpty() ? "UserDelegationKey" : rootElementName; + return xmlReader.readObject(finalRootElementName, reader -> { + UserDelegationKey deserializedUserDelegationKey = new UserDelegationKey(); + while (reader.nextElement() != XmlToken.END_ELEMENT) { + QName elementName = reader.getElementName(); + + if ("SignedOid".equals(elementName.getLocalPart())) { + deserializedUserDelegationKey.signedOid = reader.getStringElement(); + } else if ("SignedTid".equals(elementName.getLocalPart())) { + deserializedUserDelegationKey.signedTid = reader.getStringElement(); + } else if ("SignedStart".equals(elementName.getLocalPart())) { + deserializedUserDelegationKey.signedStart + = reader.getNullableElement(dateString -> CoreUtils.parseBestOffsetDateTime(dateString)); + } else if ("SignedExpiry".equals(elementName.getLocalPart())) { + deserializedUserDelegationKey.signedExpiry + = reader.getNullableElement(dateString -> CoreUtils.parseBestOffsetDateTime(dateString)); + } else if ("SignedService".equals(elementName.getLocalPart())) { + deserializedUserDelegationKey.signedService = reader.getStringElement(); + } else if ("SignedVersion".equals(elementName.getLocalPart())) { + deserializedUserDelegationKey.signedVersion = reader.getStringElement(); + } else if ("Value".equals(elementName.getLocalPart())) { + deserializedUserDelegationKey.value = reader.getStringElement(); + } else { + reader.skipElement(); + } + } + + return deserializedUserDelegationKey; + }); + } +} diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java index 9e2af2793155..a01bf981b845 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java @@ -28,18 +28,13 @@ public final class QueueServiceSasSignatureValues { .get(Constants.PROPERTY_AZURE_STORAGE_SAS_SERVICE_VERSION, QueueServiceVersion.getLatest().getVersion()); private SasProtocol protocol; - private OffsetDateTime startTime; - private OffsetDateTime expiryTime; - private String permissions; - private SasIpRange sasIpRange; - private String queueName; - private String identifier; + private String delegatedUserObjectId; /** * Creates an object with empty values for all fields. @@ -270,6 +265,30 @@ public QueueServiceSasSignatureValues setIdentifier(String identifier) { return this; } + /** + * Optional. Beginning in version 2025-07-05, this value specifies the Entra ID of the user that is authorized to + * use the resulting SAS URL. The resulting SAS URL must be used in conjunction with an Entra ID token that has been + * issued to the user specified in this value. + * + * @return The Entra ID of the user that is authorized to use the resulting SAS URL. + */ + public String getDelegatedUserObjectId() { + return delegatedUserObjectId; + } + + /** + * Optional. Beginning in version 2025-07-05, this value specifies the Entra ID of the user that is authorized to + * use the resulting SAS URL. The resulting SAS URL must be used in conjunction with an Entra ID token that has been + * issued to the user specified in this value. + * + * @param delegatedUserObjectId The Entra ID of the user that is authorized to use the resulting SAS URL. + * @return the updated BlobServiceSasSignatureValues object + */ + public QueueServiceSasSignatureValues setDelegatedUserObjectId(String delegatedUserObjectId) { + this.delegatedUserObjectId = delegatedUserObjectId; + return this; + } + /** * Uses an account's shared key credential to sign these signature values to produce the proper SAS query * parameters. @@ -322,7 +341,9 @@ private String stringToSign(String canonicalName) { return String.join("\n", this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), this.expiryTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), canonicalName, - this.identifier == null ? "" : this.identifier, this.sasIpRange == null ? "" : this.sasIpRange.toString(), + this.identifier == null ? "" : this.identifier, + this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId, + this.sasIpRange == null ? "" : this.sasIpRange.toString(), this.protocol == null ? "" : protocol.toString(), VERSION == null ? "" : VERSION); } } diff --git a/sdk/storage/azure-storage-queue/swagger/README.md b/sdk/storage/azure-storage-queue/swagger/README.md index 99294513c115..60c989cd8c4b 100644 --- a/sdk/storage/azure-storage-queue/swagger/README.md +++ b/sdk/storage/azure-storage-queue/swagger/README.md @@ -15,7 +15,7 @@ autorest ### Code generation settings ``` yaml use: '@autorest/java@4.1.52' -input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/f031b8ef2830772c5c33d1768886551f7e538b7c/specification/storage/data-plane/Microsoft.QueueStorage/stable/2018-03-28/queue.json +input-file: https://raw.githubusercontent.com/seanmcc-msft/azure-rest-api-specs/422c0661faac7c93463987d6437c6778818718f5/specification/storage/data-plane/Microsoft.QueueStorage/stable/2026-02-06/queue.json java: true output-folder: ../ namespace: com.azure.storage.queue @@ -24,7 +24,7 @@ license-header: MICROSOFT_MIT_SMALL enable-sync-stack: true default-http-exception-type: com.azure.storage.queue.implementation.models.QueueStorageExceptionInternal models-subpackage: implementation.models -custom-types: QueueErrorCode,QueueSignedIdentifier,SendMessageResult,QueueMessageItem,PeekedMessageItem,QueueItem,QueueServiceProperties,QueueServiceStatistics,QueueCorsRule,QueueAccessPolicy,QueueAnalyticsLogging,QueueMetrics,QueueRetentionPolicy,GeoReplicationStatus,GeoReplicationStatusType,GeoReplication +custom-types: QueueErrorCode,QueueSignedIdentifier,SendMessageResult,QueueMessageItem,PeekedMessageItem,QueueItem,QueueServiceProperties,QueueServiceStatistics,QueueCorsRule,QueueAccessPolicy,QueueAnalyticsLogging,QueueMetrics,QueueRetentionPolicy,GeoReplicationStatus,GeoReplicationStatusType,GeoReplication,UserDelegationKey,KeyInfo custom-types-subpackage: models customization-class: src/main/java/QueueStorageCustomization.java use-input-stream-for-binary: true From bfbfd1c57219fb15fd3b5ffc261901297de45be5 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Thu, 7 Aug 2025 11:44:13 -0700 Subject: [PATCH 07/14] async implementations and tests --- .../azure/storage/queue/QueueAsyncClient.java | 12 +-- .../com/azure/storage/queue/QueueClient.java | 8 +- .../storage/queue/QueueServiceClient.java | 8 +- .../implementation/util/QueueSasImplUtil.java | 20 ++-- .../sas/QueueServiceSasSignatureValues.java | 8 +- .../queue/QueueSasAsyncClientTests.java | 91 +++++++++++++++++++ .../storage/queue/QueueSasClientTests.java | 78 ++++++++++++++++ .../azure/storage/queue/QueueTestBase.java | 42 +++++++++ 8 files changed, 234 insertions(+), 33 deletions(-) diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java index 6e78ed63b4a5..4812492f2747 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java @@ -1525,13 +1525,12 @@ public String generateSas(QueueServiceSasSignatureValues queueServiceSasSignatur * * @param queueServiceSasSignatureValues {@link QueueServiceSasSignatureValues} * @param userDelegationKey A {@link UserDelegationKey} object used to sign the SAS values. - * @param context Additional context that is passed through the code when generating a SAS. * * @return A {@code String} representing the SAS query parameters. */ public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServiceSasSignatureValues, - UserDelegationKey userDelegationKey, Context context) { - return generateUserDelegationSas(queueServiceSasSignatureValues, userDelegationKey, null, context); + UserDelegationKey userDelegationKey) { + return generateUserDelegationSas(queueServiceSasSignatureValues, userDelegationKey, null, Context.NONE); } /** @@ -1547,9 +1546,8 @@ public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServ * @return A {@code String} representing the SAS query parameters. */ public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServiceSasSignatureValues, - UserDelegationKey userDelegationKey, Consumer stringToSignHandler, - Context context) { - return new QueueSasImplUtil(queueServiceSasSignatureValues, getQueueName()).generateUserDelegationSas(userDelegationKey, accountName, - stringToSignHandler, context); + UserDelegationKey userDelegationKey, Consumer stringToSignHandler, Context context) { + return new QueueSasImplUtil(queueServiceSasSignatureValues, getQueueName()) + .generateUserDelegationSas(userDelegationKey, accountName, stringToSignHandler, context); } } diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueClient.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueClient.java index 26cd7d5c2898..6bd3751a4328 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueClient.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueClient.java @@ -1467,13 +1467,12 @@ public String generateSas(QueueServiceSasSignatureValues queueServiceSasSignatur * * @param queueServiceSasSignatureValues {@link QueueServiceSasSignatureValues} * @param userDelegationKey A {@link UserDelegationKey} object used to sign the SAS values. - * @param context Additional context that is passed through the code when generating a SAS. * * @return A {@code String} representing the SAS query parameters. */ public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServiceSasSignatureValues, - UserDelegationKey userDelegationKey, Context context) { - return generateUserDelegationSas(queueServiceSasSignatureValues, userDelegationKey, null, context); + UserDelegationKey userDelegationKey) { + return generateUserDelegationSas(queueServiceSasSignatureValues, userDelegationKey, null, Context.NONE); } /** @@ -1489,8 +1488,7 @@ public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServ * @return A {@code String} representing the SAS query parameters. */ public String generateUserDelegationSas(QueueServiceSasSignatureValues queueServiceSasSignatureValues, - UserDelegationKey userDelegationKey, Consumer stringToSignHandler, - Context context) { + UserDelegationKey userDelegationKey, Consumer stringToSignHandler, Context context) { return new QueueSasImplUtil(queueServiceSasSignatureValues, getQueueName()) .generateUserDelegationSas(userDelegationKey, accountName, stringToSignHandler, context); } diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceClient.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceClient.java index 19ebf73272c0..778c0681027d 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceClient.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueServiceClient.java @@ -737,10 +737,10 @@ public Response getUserDelegationKeyWithResponse(OffsetDateTi Context finalContext = context == null ? Context.NONE : context; Callable> operation = () -> this.azureQueueStorage.getServices() - .getUserDelegationKeyWithResponse( - new KeyInfo().setStart(start == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(start)) - .setExpiry(Constants.ISO_8601_UTC_DATE_FORMATTER.format(expiry)), - null, null, finalContext); + .getUserDelegationKeyWithResponse( + new KeyInfo().setStart(start == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(start)) + .setExpiry(Constants.ISO_8601_UTC_DATE_FORMATTER.format(expiry)), + null, null, finalContext); ResponseBase response = sendRequest(operation, timeout, QueueStorageException.class); return new SimpleResponse<>(response, response.getValue()); diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java index d74530f1e37b..a49017b3dd45 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java @@ -148,10 +148,8 @@ private String encode(UserDelegationKey userDelegationKey, String signature) { tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_IDENTIFIER, this.identifier); if (userDelegationKey != null) { - tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_OBJECT_ID, - userDelegationKey.getSignedOid()); - tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_TENANT_ID, - userDelegationKey.getSignedTid()); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_OBJECT_ID, userDelegationKey.getSignedOid()); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_TENANT_ID, userDelegationKey.getSignedTid()); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_START, formatQueryParameterDate(new TimeAndFormat(userDelegationKey.getSignedStart(), null))); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_EXPIRY, @@ -160,7 +158,8 @@ private String encode(UserDelegationKey userDelegationKey, String signature) { userDelegationKey.getSignedService()); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_VERSION, userDelegationKey.getSignedVersion()); - tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_DELEGATED_USER_OBJECT_ID, this.delegatedUserObjectId); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_DELEGATED_USER_OBJECT_ID, + this.delegatedUserObjectId); } tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNATURE, signature); @@ -170,7 +169,7 @@ private String encode(UserDelegationKey userDelegationKey, String signature) { /** * Ensures that the builder's properties are in a consistent state. - + * 1. If there is no version, use latest. * 2. If there is no identifier set, ensure expiryTime and permissions are set. * 4. Reparse permissions depending on what the resource is. If it is an unrecognised resource, do nothing. @@ -215,17 +214,14 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) { return String.join("\n", this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), this.expiryTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), canonicalName, - key.getSignedOid() == null ? "" : key.getSignedOid(), - key.getSignedTid() == null ? "" : key.getSignedTid(), + key.getSignedOid() == null ? "" : key.getSignedOid(), key.getSignedTid() == null ? "" : key.getSignedTid(), key.getSignedStart() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedStart()), key.getSignedExpiry() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedExpiry()), key.getSignedService() == null ? "" : key.getSignedService(), - key.getSignedVersion() == null ? "" : key.getSignedVersion(), - null, // SignedKeyDelegatedUserTenantId, will be added in a future release. + key.getSignedVersion() == null ? "" : key.getSignedVersion(), null, // SignedKeyDelegatedUserTenantId, will be added in a future release. this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId, this.sasIpRange == null ? "" : this.sasIpRange.toString(), - this.protocol == null ? "" : this.protocol.toString(), - VERSION); + this.protocol == null ? "" : this.protocol.toString(), VERSION); } } diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java index a01bf981b845..a69e502a34bf 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java @@ -207,7 +207,7 @@ public QueueServiceSasSignatureValues setSasIpRange(SasIpRange sasIpRange) { * Gets the name of the queue this SAS may access. * * @return The name of the queue the SAS user may access. - * @deprecated Queue name is now auto-populated by the SAS generation methods provided on the desired queue client. + * @deprecated Queue name is now autopopulated by the SAS generation methods provided on the desired queue client. */ @Deprecated public String getQueueName() { @@ -220,7 +220,7 @@ public String getQueueName() { * @param queueName Canonical name of the object the SAS grants access * @return the updated QueueServiceSasSignatureValues object * @deprecated Please use the generateSas methods provided on the desired queue client that will - * auto-populate the queue name. + * autopopulate the queue name. */ @Deprecated public QueueServiceSasSignatureValues setQueueName(String queueName) { @@ -341,9 +341,7 @@ private String stringToSign(String canonicalName) { return String.join("\n", this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), this.expiryTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), canonicalName, - this.identifier == null ? "" : this.identifier, - this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId, - this.sasIpRange == null ? "" : this.sasIpRange.toString(), + this.identifier == null ? "" : this.identifier, this.sasIpRange == null ? "" : this.sasIpRange.toString(), this.protocol == null ? "" : protocol.toString(), VERSION == null ? "" : VERSION); } } diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java index 33884b813d1d..111932e06f50 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java @@ -3,19 +3,29 @@ package com.azure.storage.queue; +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.rest.Response; import com.azure.storage.common.sas.AccountSasPermission; import com.azure.storage.common.sas.AccountSasResourceType; import com.azure.storage.common.sas.AccountSasService; import com.azure.storage.common.sas.AccountSasSignatureValues; import com.azure.storage.common.sas.SasProtocol; +import com.azure.storage.common.test.shared.StorageCommonTestUtils; +import com.azure.storage.common.test.shared.extensions.LiveOnly; +import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; import com.azure.storage.queue.models.QueueAccessPolicy; +import com.azure.storage.queue.models.QueueErrorCode; +import com.azure.storage.queue.models.QueueProperties; import com.azure.storage.queue.models.QueueSignedIdentifier; import com.azure.storage.queue.models.QueueStorageException; import com.azure.storage.queue.models.SendMessageResult; +import com.azure.storage.queue.models.UserDelegationKey; import com.azure.storage.queue.sas.QueueSasPermission; import com.azure.storage.queue.sas.QueueServiceSasSignatureValues; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.time.Duration; @@ -23,6 +33,9 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; +import static com.azure.storage.common.test.shared.StorageCommonTestUtils.getOidFromToken; +import static com.azure.storage.queue.QueueTestHelper.assertExceptionStatusCodeAndMessage; +import static com.azure.storage.queue.QueueTestHelper.assertResponseStatusCode; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -160,4 +173,82 @@ public void accountSasListQueues() { assertDoesNotThrow(() -> sc.listQueues().next().block() != null); } + + // RBAC replication lag + @Test + @LiveOnly + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void queueSasUserDelegationDelegatedObjectId() { + liveTestScenarioWithRetry(() -> { + QueueSasPermission permissions = new QueueSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + QueueServiceSasSignatureValues sasValues + = new QueueServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + + Flux> response = getUserDelegationInfo().flatMapMany(key -> { + String sas = asyncSasClient.generateUserDelegationSas(sasValues, key); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. + QueueAsyncClient client = instrument(new QueueClientBuilder().endpoint(asyncSasClient.getQueueUrl()) + .sasToken(sas) + .credential(tokenCredential)).buildAsyncClient(); + + return client.getPropertiesWithResponse(); + }); + + StepVerifier.create(response).assertNext(r -> assertResponseStatusCode(r, 200)).verifyComplete(); + }); + } + + // RBAC replication lag + @Test + @LiveOnly + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void queueSasUserDelegationDelegatedObjectIdFail() { + liveTestScenarioWithRetry(() -> { + QueueSasPermission permissions = new QueueSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + QueueServiceSasSignatureValues sasValues + = new QueueServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + + Flux> response = getUserDelegationInfo().flatMapMany(key -> { + String sas = asyncSasClient.generateUserDelegationSas(sasValues, key); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. + QueueAsyncClient client + = instrument(new QueueClientBuilder().endpoint(asyncSasClient.getQueueUrl()).sasToken(sas)) + .buildAsyncClient(); + + return client.getPropertiesWithResponse(); + }); + + StepVerifier.create(response).verifyErrorSatisfies(e -> { + assertExceptionStatusCodeAndMessage(e, 403, QueueErrorCode.AUTHENTICATION_FAILED); + }); + }); + } + + private Mono getUserDelegationInfo() { + return getOAuthServiceAsyncClient() + .getUserDelegationKey(testResourceNamer.now().minusDays(1), testResourceNamer.now().plusDays(1)) + .flatMap(r -> { + String keyOid = testResourceNamer.recordValueFromConfig(r.getSignedOid()); + r.setSignedOid(keyOid); + String keyTid = testResourceNamer.recordValueFromConfig(r.getSignedTid()); + r.setSignedTid(keyTid); + return Mono.just(r); + }); + } } diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java index d7b208bcc0c8..7af5027c05a0 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java @@ -3,16 +3,25 @@ package com.azure.storage.queue; +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.rest.Response; +import com.azure.core.util.Context; import com.azure.storage.common.sas.AccountSasPermission; import com.azure.storage.common.sas.AccountSasResourceType; import com.azure.storage.common.sas.AccountSasService; import com.azure.storage.common.sas.AccountSasSignatureValues; import com.azure.storage.common.sas.SasProtocol; +import com.azure.storage.common.test.shared.StorageCommonTestUtils; +import com.azure.storage.common.test.shared.extensions.LiveOnly; +import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; import com.azure.storage.queue.models.QueueAccessPolicy; +import com.azure.storage.queue.models.QueueErrorCode; import com.azure.storage.queue.models.QueueMessageItem; +import com.azure.storage.queue.models.QueueProperties; import com.azure.storage.queue.models.QueueSignedIdentifier; import com.azure.storage.queue.models.QueueStorageException; import com.azure.storage.queue.models.SendMessageResult; +import com.azure.storage.queue.models.UserDelegationKey; import com.azure.storage.queue.sas.QueueSasPermission; import com.azure.storage.queue.sas.QueueServiceSasSignatureValues; import org.junit.jupiter.api.BeforeEach; @@ -24,6 +33,9 @@ import java.util.Arrays; import java.util.Iterator; +import static com.azure.storage.common.test.shared.StorageCommonTestUtils.getOidFromToken; +import static com.azure.storage.queue.QueueTestHelper.assertExceptionStatusCodeAndMessage; +import static com.azure.storage.queue.QueueTestHelper.assertResponseStatusCode; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -172,4 +184,70 @@ public void rememberAboutStringToSignDeprecation() { assertEquals(deprecatedStringToSign, client.generateSas(values)); } + + // RBAC replication lag + @Test + @LiveOnly + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void queueSasUserDelegationDelegatedObjectId() { + liveTestScenarioWithRetry(() -> { + QueueSasPermission permissions = new QueueSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + QueueServiceSasSignatureValues sasValues + = new QueueServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + String sas = sasClient.generateUserDelegationSas(sasValues, getUserDelegationInfo()); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. + QueueClient client = instrument( + new QueueClientBuilder().endpoint(sasClient.getQueueUrl()).sasToken(sas).credential(tokenCredential)) + .buildClient(); + + Response response = client.getPropertiesWithResponse(null, Context.NONE); + assertResponseStatusCode(response, 200); + }); + } + + // RBAC replication lag + @Test + @LiveOnly + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void queueSasUserDelegationDelegatedObjectIdFail() { + liveTestScenarioWithRetry(() -> { + QueueSasPermission permissions = new QueueSasPermission().setReadPermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + TokenCredential tokenCredential = StorageCommonTestUtils.getTokenCredential(interceptorManager); + + // We need to get the object ID from the token credential used to authenticate the request + String oid = getOidFromToken(tokenCredential); + QueueServiceSasSignatureValues sasValues + = new QueueServiceSasSignatureValues(expiryTime, permissions).setDelegatedUserObjectId(oid); + String sas = sasClient.generateUserDelegationSas(sasValues, getUserDelegationInfo()); + + // When a delegated user object ID is set, the client must be authenticated with both the SAS and the + // token credential. Token credential is not provided here, so the request should fail. + QueueClient client + = instrument(new QueueClientBuilder().endpoint(sasClient.getQueueUrl()).sasToken(sas)).buildClient(); + + QueueStorageException e + = assertThrows(QueueStorageException.class, () -> client.getPropertiesWithResponse(null, Context.NONE)); + assertExceptionStatusCodeAndMessage(e, 403, QueueErrorCode.AUTHENTICATION_FAILED); + }); + } + + protected UserDelegationKey getUserDelegationInfo() { + UserDelegationKey key = getOAuthServiceClient().getUserDelegationKey(testResourceNamer.now().minusDays(1), + testResourceNamer.now().plusDays(1)); + String keyOid = testResourceNamer.recordValueFromConfig(key.getSignedOid()); + key.setSignedOid(keyOid); + String keyTid = testResourceNamer.recordValueFromConfig(key.getSignedTid()); + key.setSignedTid(keyTid); + return key; + } } diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java index 23b94a8041ca..c9f0e060d608 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java @@ -16,6 +16,8 @@ import com.azure.storage.common.test.shared.TestEnvironment; import com.azure.storage.common.test.shared.policy.PerCallVersionPolicy; import com.azure.storage.queue.models.QueuesSegmentOptions; +import com.azure.storage.queue.models.UserDelegationKey; +import reactor.core.publisher.Mono; import java.time.Duration; import java.util.Arrays; @@ -145,4 +147,44 @@ protected , E extends Enum> T instrument(T builder) { protected String getPrimaryConnectionString() { return ENVIRONMENT.getPrimaryAccount().getConnectionString(); } + + protected void liveTestScenarioWithRetry(Runnable runnable) { + if (!interceptorManager.isLiveMode()) { + runnable.run(); + return; + } + + int retry = 0; + + // Try up to 4 times + while (retry < 4) { + try { + runnable.run(); + return; // success + } catch (Exception ex) { + retry++; + sleepIfRunningAgainstService(5000); + } + } + // Final attempt (5th try) + runnable.run(); + } + + protected QueueServiceClient getOAuthServiceClient() { + QueueServiceClientBuilder builder + = new QueueServiceClientBuilder().endpoint(ENVIRONMENT.getPrimaryAccount().getQueueEndpoint()); + + instrument(builder); + + return builder.credential(StorageCommonTestUtils.getTokenCredential(interceptorManager)).buildClient(); + } + + protected QueueServiceAsyncClient getOAuthServiceAsyncClient() { + QueueServiceClientBuilder builder + = new QueueServiceClientBuilder().endpoint(ENVIRONMENT.getPrimaryAccount().getQueueEndpoint()); + + instrument(builder); + + return builder.credential(StorageCommonTestUtils.getTokenCredential(interceptorManager)).buildAsyncClient(); + } } From ab2f396fdcbf1a3621692b51b0fa1c9a3f6d3bc5 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Thu, 7 Aug 2025 11:54:15 -0700 Subject: [PATCH 08/14] fixing typo --- .../azure/storage/queue/sas/QueueServiceSasSignatureValues.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java index a69e502a34bf..dcece844a663 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/sas/QueueServiceSasSignatureValues.java @@ -282,7 +282,7 @@ public String getDelegatedUserObjectId() { * issued to the user specified in this value. * * @param delegatedUserObjectId The Entra ID of the user that is authorized to use the resulting SAS URL. - * @return the updated BlobServiceSasSignatureValues object + * @return the updated QueueServiceSasSignatureValues object */ public QueueServiceSasSignatureValues setDelegatedUserObjectId(String delegatedUserObjectId) { this.delegatedUserObjectId = delegatedUserObjectId; From 9c9486eec5e412a9ee29f1110e8ba3e25aee97a9 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Thu, 7 Aug 2025 13:29:27 -0700 Subject: [PATCH 09/14] style --- .../src/main/java/com/azure/storage/queue/QueueAsyncClient.java | 1 - .../src/test/java/com/azure/storage/queue/QueueTestBase.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java index 4812492f2747..57ef891385dd 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/QueueAsyncClient.java @@ -44,7 +44,6 @@ import reactor.core.scheduler.Schedulers; import java.time.Duration; -import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.List; diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java index c9f0e060d608..5cd0420fd433 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java @@ -16,8 +16,6 @@ import com.azure.storage.common.test.shared.TestEnvironment; import com.azure.storage.common.test.shared.policy.PerCallVersionPolicy; import com.azure.storage.queue.models.QueuesSegmentOptions; -import com.azure.storage.queue.models.UserDelegationKey; -import reactor.core.publisher.Mono; import java.time.Duration; import java.util.Arrays; From 571d299799a4a4ff9a63c3a057192dce112e271f Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 13 Aug 2025 09:36:55 -0700 Subject: [PATCH 10/14] adding send message with user delegation sas test and service tests --- .../implementation/util/QueueSasImplUtil.java | 2 +- .../storage/queue/QueueAsyncApiTests.java | 2 +- .../storage/queue/QueueSasClientTests.java | 26 ++++++++++++++++ .../storage/queue/QueueServiceApiTests.java | 28 +++++++++++++++++ .../queue/QueueServiceAsyncApiTests.java | 31 +++++++++++++++++-- .../azure/storage/queue/QueueTestBase.java | 4 ++- 6 files changed, 88 insertions(+), 5 deletions(-) diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java index a49017b3dd45..e12778d68eb3 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java @@ -218,7 +218,7 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) { key.getSignedStart() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedStart()), key.getSignedExpiry() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedExpiry()), key.getSignedService() == null ? "" : key.getSignedService(), - key.getSignedVersion() == null ? "" : key.getSignedVersion(), null, // SignedKeyDelegatedUserTenantId, will be added in a future release. + key.getSignedVersion() == null ? "" : key.getSignedVersion(), "", // SignedKeyDelegatedUserTenantId, will be added in a future release. this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId, this.sasIpRange == null ? "" : this.sasIpRange.toString(), this.protocol == null ? "" : this.protocol.toString(), VERSION); diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueAsyncApiTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueAsyncApiTests.java index 9d06fc7aa2ec..7e59683ec577 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueAsyncApiTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueAsyncApiTests.java @@ -897,7 +897,7 @@ public void audienceFromString() { @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2025-07-05") public void getSetAccessPolicyOAuth() { // Arrange - QueueServiceAsyncClient service = getOAuthQueueAsyncServiceClient(); + QueueServiceAsyncClient service = getOAuthQueueServiceAsyncClient(); Mono createQueue = queueAsyncClient.createIfNotExists(); queueAsyncClient = service.getQueueAsyncClient(queueName); diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java index 7af5027c05a0..b4e995a04bec 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java @@ -36,9 +36,11 @@ import static com.azure.storage.common.test.shared.StorageCommonTestUtils.getOidFromToken; import static com.azure.storage.queue.QueueTestHelper.assertExceptionStatusCodeAndMessage; import static com.azure.storage.queue.QueueTestHelper.assertResponseStatusCode; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class QueueSasClientTests extends QueueTestBase { private QueueClient sasClient; @@ -241,6 +243,30 @@ public void queueSasUserDelegationDelegatedObjectIdFail() { }); } + @Test + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void sendMessageUserDelegationSAS() { + liveTestScenarioWithRetry(() -> { + QueueSasPermission permissions = new QueueSasPermission().setReadPermission(true) + .setAddPermission(true) + .setProcessPermission(true) + .setUpdatePermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + QueueServiceSasSignatureValues sasValues = new QueueServiceSasSignatureValues(expiryTime, permissions); + String sas = sasClient.generateUserDelegationSas(sasValues, getUserDelegationInfo()); + + QueueClient client + = instrument(new QueueClientBuilder().endpoint(sasClient.getQueueUrl()).sasToken(sas)).buildClient(); + + client.sendMessage(DATA.getDefaultBinaryData()); + Iterator dequeueMsgIter = client.receiveMessages(2).iterator(); + assertTrue(dequeueMsgIter.hasNext()); + dequeueMsgIter.next(); // Skip the first message, which is the one we sent in the setup + assertArrayEquals(DATA.getDefaultBytes(), dequeueMsgIter.next().getBody().toBytes()); + }); + } + protected UserDelegationKey getUserDelegationInfo() { UserDelegationKey key = getOAuthServiceClient().getUserDelegationKey(testResourceNamer.now().minusDays(1), testResourceNamer.now().plusDays(1)); diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceApiTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceApiTests.java index 8e5901c27f1c..0c98ae65d2b8 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceApiTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceApiTests.java @@ -6,6 +6,7 @@ import com.azure.core.http.rest.PagedIterable; import com.azure.core.http.rest.PagedResponse; import com.azure.core.http.rest.Response; +import com.azure.core.util.Context; import com.azure.identity.DefaultAzureCredentialBuilder; import com.azure.storage.common.test.shared.extensions.LiveOnly; import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; @@ -18,6 +19,7 @@ import com.azure.storage.queue.models.QueueServiceProperties; import com.azure.storage.queue.models.QueueStorageException; import com.azure.storage.queue.models.QueuesSegmentOptions; +import com.azure.storage.queue.models.UserDelegationKey; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceLock; @@ -27,6 +29,8 @@ import java.net.MalformedURLException; import java.net.URL; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -272,4 +276,28 @@ public void audienceFromString() { assertNotNull(aadService.getProperties()); } + + @Test + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void queueServiceGetUserDelegationKey() { + QueueServiceClient oAuthServiceClient = getOAuthQueueServiceClient(); + + OffsetDateTime expiry = testResourceNamer.now().plusHours(1).truncatedTo(ChronoUnit.SECONDS); + Response response + = oAuthServiceClient.getUserDelegationKeyWithResponse(testResourceNamer.now(), expiry, null, Context.NONE); + + assertEquals(expiry, response.getValue().getSignedExpiry()); + } + + @Test + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void queueServiceGetUserDelegationKeyAuthError() { + OffsetDateTime expiry = testResourceNamer.now().plusHours(1).truncatedTo(ChronoUnit.SECONDS); + + //not oauth client + QueueStorageException e = assertThrows(QueueStorageException.class, () -> primaryQueueServiceClient + .getUserDelegationKeyWithResponse(testResourceNamer.now(), expiry, null, Context.NONE)); + + QueueTestHelper.assertExceptionStatusCodeAndMessage(e, 403, QueueErrorCode.AUTHENTICATION_FAILED); + } } diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceAsyncApiTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceAsyncApiTests.java index b54015b65c24..cc79d2262435 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceAsyncApiTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceAsyncApiTests.java @@ -15,6 +15,7 @@ import com.azure.storage.queue.models.QueueRetentionPolicy; import com.azure.storage.queue.models.QueueServiceProperties; import com.azure.storage.queue.models.QueuesSegmentOptions; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceLock; @@ -25,6 +26,8 @@ import java.net.MalformedURLException; import java.net.URL; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -235,7 +238,7 @@ public void audienceErrorBearerChallengeRetry() { = getOAuthServiceClientBuilder().audience(QueueAudience.createQueueServiceAccountAudience("badaudience")) .buildAsyncClient(); - StepVerifier.create(aadService.getProperties()).assertNext(r -> assertNotNull(r)).verifyComplete(); + StepVerifier.create(aadService.getProperties()).assertNext(Assertions::assertNotNull).verifyComplete(); } @Test @@ -246,6 +249,30 @@ public void audienceFromString() { QueueServiceAsyncClient aadService = getOAuthServiceClientBuilder().audience(audience).buildAsyncClient(); - StepVerifier.create(aadService.getProperties()).assertNext(r -> assertNotNull(r)).verifyComplete(); + StepVerifier.create(aadService.getProperties()).assertNext(Assertions::assertNotNull).verifyComplete(); + } + + @Test + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void queueServiceGetUserDelegationKey() { + QueueServiceAsyncClient oAuthServiceClient = getOAuthQueueServiceAsyncClient(); + + OffsetDateTime expiry = testResourceNamer.now().plusHours(1).truncatedTo(ChronoUnit.SECONDS); + StepVerifier.create(oAuthServiceClient.getUserDelegationKeyWithResponse(testResourceNamer.now(), expiry)) + .assertNext(r -> assertEquals(expiry, r.getValue().getSignedExpiry())) + .verifyComplete(); + } + + @Test + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void queueServiceGetUserDelegationKeyAuthError() { + OffsetDateTime expiry = testResourceNamer.now().plusHours(1).truncatedTo(ChronoUnit.SECONDS); + + //not oauth client + StepVerifier.create(primaryQueueServiceAsyncClient + .getUserDelegationKeyWithResponse(testResourceNamer.now(), expiry)) + .verifyErrorSatisfies(e -> + QueueTestHelper.assertExceptionStatusCodeAndMessage(e, 403, + QueueErrorCode.AUTHENTICATION_FAILED)); } } diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java index 5cd0420fd433..5b785661b5b8 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueTestBase.java @@ -13,6 +13,7 @@ import com.azure.core.util.Context; import com.azure.storage.common.StorageSharedKeyCredential; import com.azure.storage.common.test.shared.StorageCommonTestUtils; +import com.azure.storage.common.test.shared.TestDataFactory; import com.azure.storage.common.test.shared.TestEnvironment; import com.azure.storage.common.test.shared.policy.PerCallVersionPolicy; import com.azure.storage.queue.models.QueuesSegmentOptions; @@ -26,6 +27,7 @@ */ public class QueueTestBase extends TestProxyTestBase { protected static final TestEnvironment ENVIRONMENT = TestEnvironment.getInstance(); + protected static final TestDataFactory DATA = TestDataFactory.getInstance(); protected String prefix; @@ -88,7 +90,7 @@ protected QueueServiceClient getOAuthQueueServiceClient() { return getOAuthServiceClientBuilder().buildClient(); } - protected QueueServiceAsyncClient getOAuthQueueAsyncServiceClient() { + protected QueueServiceAsyncClient getOAuthQueueServiceAsyncClient() { return getOAuthServiceClientBuilder().buildAsyncClient(); } From 2e765d829c1d303eec407f5fd7c62144bda29476 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Tue, 19 Aug 2025 16:30:42 -0700 Subject: [PATCH 11/14] expanding tid and oid acronyms --- .../queue/models/UserDelegationKey.java | 44 +++++++++---------- .../queue/QueueSasAsyncClientTests.java | 8 ++-- .../storage/queue/QueueSasClientTests.java | 8 ++-- .../azure-storage-queue/swagger/README.md | 10 +++++ 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/UserDelegationKey.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/UserDelegationKey.java index 13dbb1e040b4..1fb20470a5ac 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/UserDelegationKey.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/models/UserDelegationKey.java @@ -25,13 +25,13 @@ public final class UserDelegationKey implements XmlSerializable CoreUtils.parseBestOffsetDateTime(dateString)); diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java index 111932e06f50..da3d0e9a1e6c 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java @@ -244,10 +244,10 @@ private Mono getUserDelegationInfo() { return getOAuthServiceAsyncClient() .getUserDelegationKey(testResourceNamer.now().minusDays(1), testResourceNamer.now().plusDays(1)) .flatMap(r -> { - String keyOid = testResourceNamer.recordValueFromConfig(r.getSignedOid()); - r.setSignedOid(keyOid); - String keyTid = testResourceNamer.recordValueFromConfig(r.getSignedTid()); - r.setSignedTid(keyTid); + String keyOid = testResourceNamer.recordValueFromConfig(r.getSignedObjectId()); + r.setSignedObjectId(keyOid); + String keyTid = testResourceNamer.recordValueFromConfig(r.getSignedTenantId()); + r.setSignedTenantId(keyTid); return Mono.just(r); }); } diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java index b4e995a04bec..775f8849d517 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasClientTests.java @@ -270,10 +270,10 @@ public void sendMessageUserDelegationSAS() { protected UserDelegationKey getUserDelegationInfo() { UserDelegationKey key = getOAuthServiceClient().getUserDelegationKey(testResourceNamer.now().minusDays(1), testResourceNamer.now().plusDays(1)); - String keyOid = testResourceNamer.recordValueFromConfig(key.getSignedOid()); - key.setSignedOid(keyOid); - String keyTid = testResourceNamer.recordValueFromConfig(key.getSignedTid()); - key.setSignedTid(keyTid); + String keyOid = testResourceNamer.recordValueFromConfig(key.getSignedObjectId()); + key.setSignedObjectId(keyOid); + String keyTid = testResourceNamer.recordValueFromConfig(key.getSignedTenantId()); + key.setSignedTenantId(keyTid); return key; } } diff --git a/sdk/storage/azure-storage-queue/swagger/README.md b/sdk/storage/azure-storage-queue/swagger/README.md index 60c989cd8c4b..ab3d56748c6f 100644 --- a/sdk/storage/azure-storage-queue/swagger/README.md +++ b/sdk/storage/azure-storage-queue/swagger/README.md @@ -141,4 +141,14 @@ directive: $["x-ms-pageable"].itemName = "QueueItems"; ``` +### Rename UserDelegationKey SignedOid and SignedTid +``` yaml +directive: +- from: swagger-document + where: $.definitions.UserDelegationKey + transform: > + $.properties.SignedOid["x-ms-client-name"] = "signedObjectId"; + $.properties.SignedTid["x-ms-client-name"] = "signedTenantId"; +``` + From 6754b0972b055df588c8b98052329dfc8ce7c826 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Tue, 19 Aug 2025 16:45:25 -0700 Subject: [PATCH 12/14] adding new test and recordings --- sdk/storage/azure-storage-queue/assets.json | 2 +- .../implementation/util/QueueSasImplUtil.java | 9 +++-- .../queue/QueueSasAsyncClientTests.java | 37 ++++++++++++++++++- .../queue/QueueServiceAsyncApiTests.java | 9 ++--- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/sdk/storage/azure-storage-queue/assets.json b/sdk/storage/azure-storage-queue/assets.json index 707c584fd5e6..110030c496aa 100644 --- a/sdk/storage/azure-storage-queue/assets.json +++ b/sdk/storage/azure-storage-queue/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/storage/azure-storage-queue", - "Tag": "java/storage/azure-storage-queue_06da25b415" + "Tag": "java/storage/azure-storage-queue_59c44f4251" } \ No newline at end of file diff --git a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java index e12778d68eb3..c353b6690fb8 100644 --- a/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java +++ b/sdk/storage/azure-storage-queue/src/main/java/com/azure/storage/queue/implementation/util/QueueSasImplUtil.java @@ -148,8 +148,10 @@ private String encode(UserDelegationKey userDelegationKey, String signature) { tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_IDENTIFIER, this.identifier); if (userDelegationKey != null) { - tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_OBJECT_ID, userDelegationKey.getSignedOid()); - tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_TENANT_ID, userDelegationKey.getSignedTid()); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_OBJECT_ID, + userDelegationKey.getSignedObjectId()); + tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_TENANT_ID, + userDelegationKey.getSignedTenantId()); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_START, formatQueryParameterDate(new TimeAndFormat(userDelegationKey.getSignedStart(), null))); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_KEY_EXPIRY, @@ -214,7 +216,8 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) { return String.join("\n", this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), this.expiryTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), canonicalName, - key.getSignedOid() == null ? "" : key.getSignedOid(), key.getSignedTid() == null ? "" : key.getSignedTid(), + key.getSignedObjectId() == null ? "" : key.getSignedObjectId(), + key.getSignedTenantId() == null ? "" : key.getSignedTenantId(), key.getSignedStart() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedStart()), key.getSignedExpiry() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedExpiry()), key.getSignedService() == null ? "" : key.getSignedService(), diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java index da3d0e9a1e6c..055c4ecbda46 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueSasAsyncClientTests.java @@ -15,6 +15,7 @@ import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; import com.azure.storage.queue.models.QueueAccessPolicy; import com.azure.storage.queue.models.QueueErrorCode; +import com.azure.storage.queue.models.QueueMessageItem; import com.azure.storage.queue.models.QueueProperties; import com.azure.storage.queue.models.QueueSignedIdentifier; import com.azure.storage.queue.models.QueueStorageException; @@ -32,6 +33,7 @@ import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.Arrays; +import java.util.List; import static com.azure.storage.common.test.shared.StorageCommonTestUtils.getOidFromToken; import static com.azure.storage.queue.QueueTestHelper.assertExceptionStatusCodeAndMessage; @@ -234,8 +236,39 @@ public void queueSasUserDelegationDelegatedObjectIdFail() { return client.getPropertiesWithResponse(); }); - StepVerifier.create(response).verifyErrorSatisfies(e -> { - assertExceptionStatusCodeAndMessage(e, 403, QueueErrorCode.AUTHENTICATION_FAILED); + StepVerifier.create(response) + .verifyErrorSatisfies( + e -> assertExceptionStatusCodeAndMessage(e, 403, QueueErrorCode.AUTHENTICATION_FAILED)); + }); + } + + @Test + @RequiredServiceVersion(clazz = QueueServiceVersion.class, min = "2026-02-06") + public void sendMessageUserDelegationSAS() { + liveTestScenarioWithRetry(() -> { + QueueSasPermission permissions = new QueueSasPermission().setReadPermission(true) + .setAddPermission(true) + .setProcessPermission(true) + .setUpdatePermission(true); + OffsetDateTime expiryTime = testResourceNamer.now().plusHours(1); + + QueueServiceSasSignatureValues sasValues = new QueueServiceSasSignatureValues(expiryTime, permissions); + + Mono> response = getUserDelegationInfo().flatMap(key -> { + String sas = asyncSasClient.generateUserDelegationSas(sasValues, key); + + QueueAsyncClient client + = instrument(new QueueClientBuilder().endpoint(asyncSasClient.getQueueUrl()).sasToken(sas)) + .buildAsyncClient(); + + return client.sendMessage(DATA.getDefaultBinaryData()).then(client.receiveMessages(2).collectList()); + }); + + StepVerifier.create(response).assertNext(messageItemList -> { + // The first message is the one sent in setup. + assertEquals(2, messageItemList.size()); + assertEquals("test", messageItemList.get(0).getBody().toString()); + assertEquals(DATA.getDefaultText(), messageItemList.get(1).getBody().toString()); }); }); } diff --git a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceAsyncApiTests.java b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceAsyncApiTests.java index cc79d2262435..ae16863899a8 100644 --- a/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceAsyncApiTests.java +++ b/sdk/storage/azure-storage-queue/src/test/java/com/azure/storage/queue/QueueServiceAsyncApiTests.java @@ -269,10 +269,9 @@ public void queueServiceGetUserDelegationKeyAuthError() { OffsetDateTime expiry = testResourceNamer.now().plusHours(1).truncatedTo(ChronoUnit.SECONDS); //not oauth client - StepVerifier.create(primaryQueueServiceAsyncClient - .getUserDelegationKeyWithResponse(testResourceNamer.now(), expiry)) - .verifyErrorSatisfies(e -> - QueueTestHelper.assertExceptionStatusCodeAndMessage(e, 403, - QueueErrorCode.AUTHENTICATION_FAILED)); + StepVerifier + .create(primaryQueueServiceAsyncClient.getUserDelegationKeyWithResponse(testResourceNamer.now(), expiry)) + .verifyErrorSatisfies( + e -> QueueTestHelper.assertExceptionStatusCodeAndMessage(e, 403, QueueErrorCode.AUTHENTICATION_FAILED)); } } From 659456f0afb584edfa355b6c677c1b7fb97a304c Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 20 Aug 2025 12:20:09 -0700 Subject: [PATCH 13/14] removing change in blobsasimplutil --- .../implementation/util/BlobSasImplUtil.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/util/BlobSasImplUtil.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/util/BlobSasImplUtil.java index 5c90efa90d73..76803a1ed4b6 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/util/BlobSasImplUtil.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/implementation/util/BlobSasImplUtil.java @@ -271,7 +271,6 @@ private String encode(UserDelegationKey userDelegationKey, String signature) { tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_RESOURCE, this.resource); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNATURE, signature); - tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_DELEGATED_USER_OBJECT_ID, this.delegatedUserObjectId); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_ENCRYPTION_SCOPE, this.encryptionScope); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CACHE_CONTROL, this.cacheControl); tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CONTENT_DISPOSITION, this.contentDisposition); @@ -444,30 +443,6 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) { this.contentEncoding == null ? "" : this.contentEncoding, this.contentLanguage == null ? "" : this.contentLanguage, this.contentType == null ? "" : this.contentType); - } else if (VERSION.compareTo(BlobServiceVersion.V2025_11_05.getVersion()) <= 0) { - return String.join("\n", this.permissions == null ? "" : this.permissions, - this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), - this.expiryTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), - canonicalName, key.getSignedObjectId() == null ? "" : key.getSignedObjectId(), - key.getSignedTenantId() == null ? "" : key.getSignedTenantId(), - key.getSignedStart() == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedStart()), - key.getSignedExpiry() == null - ? "" - : Constants.ISO_8601_UTC_DATE_FORMATTER.format(key.getSignedExpiry()), - key.getSignedService() == null ? "" : key.getSignedService(), - key.getSignedVersion() == null ? "" : key.getSignedVersion(), - this.authorizedAadObjectId == null ? "" : this.authorizedAadObjectId, - "", /* suoid - empty since this applies to HNS only accounts. */ - this.correlationId == null ? "" : this.correlationId, "", /* new schema 2025-07-05 */ - this.delegatedUserObjectId == null ? "" : this.delegatedUserObjectId, - this.sasIpRange == null ? "" : this.sasIpRange.toString(), - this.protocol == null ? "" : this.protocol.toString(), VERSION, resource, - versionSegment == null ? "" : versionSegment, this.encryptionScope == null ? "" : this.encryptionScope, - this.cacheControl == null ? "" : this.cacheControl, - this.contentDisposition == null ? "" : this.contentDisposition, - this.contentEncoding == null ? "" : this.contentEncoding, - this.contentLanguage == null ? "" : this.contentLanguage, - this.contentType == null ? "" : this.contentType); } else { return String.join("\n", this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : Constants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), From 65345b90d8c2bf7b458929a650ce6bdd8b84cb14 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Wed, 20 Aug 2025 12:34:10 -0700 Subject: [PATCH 14/14] updating recordings --- sdk/storage/azure-storage-queue/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure-storage-queue/assets.json b/sdk/storage/azure-storage-queue/assets.json index 110030c496aa..e5a47f0ac06e 100644 --- a/sdk/storage/azure-storage-queue/assets.json +++ b/sdk/storage/azure-storage-queue/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/storage/azure-storage-queue", - "Tag": "java/storage/azure-storage-queue_59c44f4251" + "Tag": "java/storage/azure-storage-queue_2e02d46abe" } \ No newline at end of file