diff --git a/sdk/storage/azure-storage-blob/assets.json b/sdk/storage/azure-storage-blob/assets.json index a2bc9bdaa663..f9a5c195881a 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_f26563826e" + "Tag": "java/storage/azure-storage-blob_6631ad464e" } 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 6e6a0f25d112..fdc2f0bd3fcb 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 @@ -45,7 +45,9 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Stream; @@ -1064,10 +1066,12 @@ 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 delegatedUserObjectId, String expectedStringToSign) { + String encryptionScope, String delegatedUserObjectId, Map requestHeaders, + Map requestQueryParameters, 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); + ArrayList stringToSign = new ArrayList<>(); String expected = String.format(expectedStringToSign, ENVIRONMENT.getPrimaryAccount().getName()); @@ -1087,7 +1091,9 @@ public void blobSasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTim .setContentType(type) .setPreauthorizedAgentObjectId(saoid) .setCorrelationId(cid) - .setDelegatedUserObjectId(delegatedUserObjectId); + .setDelegatedUserObjectId(delegatedUserObjectId) + .setRequestHeaders(requestHeaders) + .setRequestQueryParameters(requestQueryParameters); UserDelegationKey key = new UserDelegationKey().setSignedObjectId(keyOid) .setSignedTenantId(keyTid) @@ -1099,11 +1105,12 @@ public void blobSasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTim BlobSasImplUtil implUtil = new BlobSasImplUtil(v, "containerName", "blobName", snapId, versionId, encryptionScope); - String sasToken - = implUtil.generateUserDelegationSas(key, ENVIRONMENT.getPrimaryAccount().getName(), Context.NONE); + String sasToken = implUtil.generateUserDelegationSas(key, ENVIRONMENT.getPrimaryAccount().getName(), + stringToSign::add, Context.NONE); CommonSasQueryParameters token = BlobUrlParts.parse(cc.getBlobContainerUrl() + "?" + sasToken).getCommonSasQueryParameters(); + assertEqualsForEachLine(stringToSign, expected); assertEquals(token.getSignature(), StorageImplUtils.computeHMac256(key.getValue(), expected)); } @@ -1111,10 +1118,25 @@ public void blobSasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTim We test string to sign functionality directly related toUserDelegation sas specific parameters */ private static Stream blobSasImplUtilStringToSignUserDelegationKeySupplier() { + // Use LinkedHashMap to ensure deterministic iteration order + Map singleHeader = new LinkedHashMap<>(); + singleHeader.put("x-ms-encryption-key-sha256", "hashvalue"); + + Map singleQueryParam = new LinkedHashMap<>(); + singleQueryParam.put("comp", "blocklist"); + + Map multipleHeaders = new LinkedHashMap<>(); + multipleHeaders.put("x-ms-encryption-key-sha256", "hashvalue"); + multipleHeaders.put("x-ms-source-if-match", "etag"); + + Map multipleQueryParams = new LinkedHashMap<>(); + multipleQueryParams.put("blockid", "blockidvalue"); + multipleQueryParams.put("comp", "blocklist"); + 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, null, null, null, "r\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) @@ -1122,145 +1144,265 @@ 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\n\n\n\n\n\n"), + + "\nb\n\n\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, 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\n11111111-1111-1111-1111-111111111111\n\n\n\n\n\n\n\n\n\n\n\n\n" - + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, 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\n22222222-2222-2222-2222-222222222222\n\n\n\n\n\n\n\n\n\n\n\n" - + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, 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\n2018-01-01T00:00:00Z\n\n\n\n\n\n\n\n\n\n\n" - + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, 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"), + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, 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\nb\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\n\n\n\n\n\n"), + + "\nb\n\n\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, 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"), + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, null, + new SasIpRange(), 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\n\n\n\n\n\n\nip\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\n\n\n\n\n\n"), + + "\nb\n\n\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, null, + SasProtocol.HTTPS_ONLY, 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\n\n\n\n\n\n\n\n" + SasProtocol.HTTPS_ONLY + "\n" - + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, + null, "snapId", 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\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nbs\nsnapId\n\n\n\n\n\n"), + + "\nbs\nsnapId\n\n\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, null, "control", 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\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\n\ncontrol\n\n\n\n"), + + "\nb\n\n\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, null, "disposition", 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 - + "\nb\n\n\n\ndisposition\n\n\n"), + + "\nb\n\n\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, null, "encoding", 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\n\n\nencoding\n\n"), + + "\nb\n\n\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, null, "language", 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\n\n\nlanguage\n"), + + "\nb\n\n\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, null, "type", 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\n\n\ntype"), + + "\nb\n\n\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, null, "versionId", 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 - + "\nbv\nversionId\n\n\n\n\n\n"), + + "\nbv\nversionId\n\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, "saoid", null, null, null, + null, null, null, null, null, null, null, null, "saoid", 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\nsaoid\n\n\n\n\n\n\n" - + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, null, "cid", 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\ncid\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\n\n\n\n\n\n"), + + "\nb\n\n\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, null, "encryptionScope", 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\nencryptionScope\n\n\n\n\n"), + + "\nb\n\nencryptionScope\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, null, "delegatedOid", + null, null, null, null, null, null, null, null, null, null, null, "delegatedOid", 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\ndelegatedOid\n\n\n" - + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n")); + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, null, "delegatedOid", 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\ndelegatedOid\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, null, "delegatedOid", 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\ndelegatedOid\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, null, "delegatedOid", 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\ndelegatedOid\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\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, null, "delegatedOid", 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\ndelegatedOid\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n\n\n"), + // Test requestHeaders only (single header) + Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, + null, null, null, null, null, null, null, null, null, null, null, null, singleHeader, 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\nx-ms-encryption-key-sha256:hashvalue\n\n\n\n\n\n\n"), + // Test requestQueryParameters only (single param) + Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, + null, null, null, null, null, null, null, null, null, null, null, null, null, singleQueryParam, + "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\ncomp:blocklist\n\n\n\n\n"), + // Test both requestHeaders and requestQueryParameters + Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, + null, null, null, null, null, null, null, null, null, null, null, null, singleHeader, singleQueryParam, + "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\nx-ms-encryption-key-sha256:hashvalue\n\n\ncomp:blocklist\n\n\n\n\n"), + // Test multiple headers and multiple query parameters + Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, + null, null, null, null, null, null, null, null, null, null, null, null, multipleHeaders, + multipleQueryParams, + "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\nx-ms-encryption-key-sha256:hashvalue\n" + + "x-ms-source-if-match:etag\n\n\nblockid:blockidvalue\n" + "comp:blocklist\n\n\n\n\n"), + // Test with all parameters populated + Arguments.of(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), // startTime + "11111111-1111-1111-1111-111111111111", // keyOid + "22222222-2222-2222-2222-222222222222", // keyTid + OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC), // keyStart + OffsetDateTime.of(LocalDateTime.of(2018, 6, 1, 0, 0), ZoneOffset.UTC), // keyExpiry + "b", // keyService + "2018-06-17", // keyVersion + "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", // keyValue + new SasIpRange(), // ipRange + SasProtocol.HTTPS_ONLY, // protocol + "snapId", // snapId + "control", // cacheControl + "disposition", // contentDisposition + "encoding", // contentEncoding + "language", // contentLanguage + "type", // contentType + null, // versionId, versionId and snapId are mutually exclusive + "saoid", // saoid (preauthorizedAgentObjectId) + "cid", // cid (correlationId) + "encryptionScope", // encryptionScope + "delegatedOid", // delegatedUserObjectId + multipleHeaders, // requestHeaders + multipleQueryParams, // requestQueryParameters + "r\n" // permissions + + Constants.ISO_8601_UTC_DATE_FORMATTER + .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) // startTime + + "\n" + + Constants.ISO_8601_UTC_DATE_FORMATTER + .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) // expiryTime + + "\n/blob/%s/containerName/blobName\n" // canonicalName + + "11111111-1111-1111-1111-111111111111\n" // keyOid + + "22222222-2222-2222-2222-222222222222\n" // keyTid + + "2018-01-01T00:00:00Z\n" // keyStart + + "2018-06-01T00:00:00Z\n" // keyExpiry + + "b\n" // keyService + + "2018-06-17\n" // keyVersion + + "saoid\n" // saoid (preauthorizedAgentObjectId) + + "\n" // suoid (always empty) + + "cid\n" // cid (correlationId) + + "\n" // delegatedUserTenantId (removed - empty) + + "delegatedOid\n" // delegatedUserObjectId + + "ip\n" // sasIpRange + + SasProtocol.HTTPS_ONLY + "\n" // protocol + + Constants.SAS_SERVICE_VERSION + "\n" // VERSION + + "bs\n" // resource (blob snapshot) + + "snapId\n" // snapId (versionSegment with snapId) + + "encryptionScope\n" // encryptionScope + + "x-ms-encryption-key-sha256:hashvalue\n" // requestHeaders (multiple) + + "x-ms-source-if-match:etag\n\n" // requestHeaders continuation + newline separator + + "\nblockid:blockidvalue\n" // requestQueryParameters (multiple, with prepended newline) + + "comp:blocklist\n" // requestQueryParameters continuation + + "control\n" // cacheControl + + "disposition\n" // contentDisposition + + "encoding\n" // contentEncoding + + "language\n" // contentLanguage + + "type" // contentType (no trailing newline) + )); } @RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2020-12-06") @@ -1348,4 +1490,40 @@ private static Stream accountSasImplUtilStringToSignSupplier() { .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nencryptionScope\n")); } + + private static void assertEqualsForEachLine(ArrayList stringToSign, String expected) { + String actual = stringToSign.get(0); + if (!expected.equals(actual)) { + StringBuilder output = new StringBuilder(); + String[] expectedLines = expected.split("\n", -1); + String[] actualLines = actual.split("\n", -1); + + output.append("\n=== Line-by-Line String-to-Sign Comparison ===\n"); + output.append("Expected lines: ").append(expectedLines.length).append("\n"); + output.append("Actual lines: ").append(actualLines.length).append("\n\n"); + + int maxLines = Math.max(expectedLines.length, actualLines.length); + for (int i = 0; i < maxLines; i++) { + String expLine = i < expectedLines.length ? expectedLines[i] : ""; + String actLine = i < actualLines.length ? actualLines[i] : ""; + + if (!expLine.equals(actLine)) { + output.append("Line ").append(i).append(" differs:\n"); + output.append(" Expected: [").append(expLine).append("]\n"); + output.append(" Actual: [").append(actLine).append("]\n\n"); + } else { + output.append("Line ").append(i).append(" matches: [").append(expLine).append("]\n"); + } + } + + output.append("=== Full Expected String ===\n"); + output.append(expected.replace("\n", "\\n\n")); + output.append("\n\n=== Full Actual String ===\n"); + output.append(actual.replace("\n", "\\n\n")); + + // Print everything at once + System.out.println(output.toString()); + } + assertEquals(expected, actual, "String-to-sign mismatch"); + } } diff --git a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/SasImplUtils.java b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/SasImplUtils.java index e6717ff734ce..17aa7ffc0d17 100644 --- a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/SasImplUtils.java +++ b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/SasImplUtils.java @@ -119,6 +119,17 @@ public static Map parseQueryString(String queryParams) { return retVals; } + /** + * Formats request headers for SAS signing. + * + * @param requestHeaders The map of request headers to format. + * @return A formatted string with headers in the format "key:value" separated by newlines, or empty string if + * null/empty. Terminates each pair with a newline (\n). + * @see + * + * Version 2026-04-06 and later (Blob Storage and Data Lake Storage) + */ + public static String formatRequestHeadersForSasSigning(Map requestHeaders) { if (requestHeaders == null || requestHeaders.isEmpty()) { return ""; @@ -128,6 +139,16 @@ public static String formatRequestHeadersForSasSigning(Map reque return sb.toString(); } + /** + * Formats request headers for SAS signing. + * + * @param requestQueryParameters The map of request headers to format. + * @return A formatted string with query params in the format "key:value" separated by newlines, or empty string if + * null/empty. Prepends a newline character. Prefixes each pair with a newline (\n). + * @see + * + * Version 2026-04-06 and later (Blob Storage and Data Lake Storage) + */ public static String formatRequestQueryParametersForSasSigning(Map requestQueryParameters) { if (requestQueryParameters == null || requestQueryParameters.isEmpty()) { return ""; diff --git a/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/SasTestData.java b/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/SasTestData.java new file mode 100644 index 000000000000..5fa488b87848 --- /dev/null +++ b/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/SasTestData.java @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.storage.common.test.shared; +import com.azure.storage.common.sas.SasIpRange; +import com.azure.storage.common.sas.SasProtocol; +import org.junit.jupiter.params.provider.Arguments; +import java.time.OffsetDateTime; +/** + * Helper class to build test arguments for regular SAS string-to-sign tests. + * This is the base class that contains common fields shared by both regular SAS and user delegation SAS. + * All fields default to null, so you only need to set the ones you're testing. + *

+ * For user delegation SAS tests, use {@link UserDelegationSasTestData} which extends this class. + */ +public class SasTestData { + // Common fields for all SAS types + protected OffsetDateTime startTime; + protected SasIpRange ipRange; + protected SasProtocol protocol; + protected String cacheControl; + protected String disposition; + protected String encoding; + protected String language; + protected String type; + protected String expectedStringToSign; + // Regular SAS specific field + protected String identifier; // Signed identifier for regular SAS + public SasTestData setStartTime(OffsetDateTime startTime) { + this.startTime = startTime; + return this; + } + public SasTestData setIdentifier(String identifier) { + this.identifier = identifier; + return this; + } + public SasTestData setIpRange(SasIpRange ipRange) { + this.ipRange = ipRange; + return this; + } + public SasTestData setProtocol(SasProtocol protocol) { + this.protocol = protocol; + return this; + } + public SasTestData setCacheControl(String cacheControl) { + this.cacheControl = cacheControl; + return this; + } + public SasTestData setDisposition(String disposition) { + this.disposition = disposition; + return this; + } + public SasTestData setEncoding(String encoding) { + this.encoding = encoding; + return this; + } + public SasTestData setLanguage(String language) { + this.language = language; + return this; + } + public SasTestData setType(String type) { + this.type = type; + return this; + } + public SasTestData setExpectedStringToSign(String expectedStringToSign) { + this.expectedStringToSign = expectedStringToSign; + return this; + } + /** + * Converts to Arguments for regular SAS tests. + * Returns arguments in this order: + * startTime, identifier, ipRange, protocol, cacheControl, disposition, encoding, language, type, expectedStringToSign + * + * @return Arguments for parameterized tests matching the signature of regular SAS test methods + */ + public Arguments toArguments() { + return Arguments.of(startTime, identifier, ipRange, protocol, cacheControl, disposition, encoding, language, + type, expectedStringToSign); + } +} diff --git a/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/UserDelegationSasTestData.java b/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/UserDelegationSasTestData.java new file mode 100644 index 000000000000..c90554e1627b --- /dev/null +++ b/sdk/storage/azure-storage-common/src/test-shared/java/com/azure/storage/common/test/shared/UserDelegationSasTestData.java @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.storage.common.test.shared; + +import com.azure.storage.common.sas.SasIpRange; +import com.azure.storage.common.sas.SasProtocol; +import org.junit.jupiter.params.provider.Arguments; + +import java.time.OffsetDateTime; +import java.util.Map; + +/** + * Helper class to build test arguments for User Delegation SAS string-to-sign tests. + * Extends {@link SasTestData} to inherit common SAS fields. + * All fields default to null, so you only need to set the ones you're testing. + *

+ * Note: User delegation SAS does NOT use the 'identifier' field (that's for regular SAS). + * Request headers and query parameters are only used in user delegation SAS. + *

+ * For regular SAS tests, use {@link SasTestData} directly. + */ +public class UserDelegationSasTestData extends SasTestData { + // User delegation SAS specific fields + private String keyOid; + private String keyTid; + private OffsetDateTime keyStart; + private OffsetDateTime keyExpiry; + private String keyService; + private String keyVersion; + private String keyValue; + private Map requestHeaders; + private Map requestQueryParameters; + private String saoid; + private String suoid; + private String cid; + + // Override parent setters to return UserDelegationSasTestData for fluent API + @Override + public UserDelegationSasTestData setStartTime(OffsetDateTime startTime) { + super.setStartTime(startTime); + return this; + } + + @Override + public UserDelegationSasTestData setIpRange(SasIpRange ipRange) { + super.setIpRange(ipRange); + return this; + } + + @Override + public UserDelegationSasTestData setProtocol(SasProtocol protocol) { + super.setProtocol(protocol); + return this; + } + + @Override + public UserDelegationSasTestData setCacheControl(String cacheControl) { + super.setCacheControl(cacheControl); + return this; + } + + @Override + public UserDelegationSasTestData setDisposition(String disposition) { + super.setDisposition(disposition); + return this; + } + + @Override + public UserDelegationSasTestData setEncoding(String encoding) { + super.setEncoding(encoding); + return this; + } + + @Override + public UserDelegationSasTestData setLanguage(String language) { + super.setLanguage(language); + return this; + } + + @Override + public UserDelegationSasTestData setType(String type) { + super.setType(type); + return this; + } + + @Override + public UserDelegationSasTestData setExpectedStringToSign(String expectedStringToSign) { + super.setExpectedStringToSign(expectedStringToSign); + return this; + } + + // User delegation SAS specific setters + + public UserDelegationSasTestData setKeyOid(String keyOid) { + this.keyOid = keyOid; + return this; + } + + public UserDelegationSasTestData setKeyTid(String keyTid) { + this.keyTid = keyTid; + return this; + } + + public UserDelegationSasTestData setKeyStart(OffsetDateTime keyStart) { + this.keyStart = keyStart; + return this; + } + + public UserDelegationSasTestData setKeyExpiry(OffsetDateTime keyExpiry) { + this.keyExpiry = keyExpiry; + return this; + } + + public UserDelegationSasTestData setKeyService(String keyService) { + this.keyService = keyService; + return this; + } + + public UserDelegationSasTestData setKeyVersion(String keyVersion) { + this.keyVersion = keyVersion; + return this; + } + + public UserDelegationSasTestData setKeyValue(String keyValue) { + this.keyValue = keyValue; + return this; + } + + public UserDelegationSasTestData setRequestHeaders(Map requestHeaders) { + this.requestHeaders = requestHeaders; + return this; + } + + public UserDelegationSasTestData setRequestQueryParameters(Map requestQueryParameters) { + this.requestQueryParameters = requestQueryParameters; + return this; + } + + public UserDelegationSasTestData setSaoid(String saoid) { + this.saoid = saoid; + return this; + } + + public UserDelegationSasTestData setSuoid(String suoid) { + this.suoid = suoid; + return this; + } + + public UserDelegationSasTestData setCid(String cid) { + this.cid = cid; + return this; + } + + /** + * Converts to Arguments for user delegation SAS tests. + * Returns arguments with or without request headers/query parameters based on the parameter. + * + * @param withHeadersAndParams Whether to include request headers and query parameters in the test data. + * @return Arguments for parameterized tests matching the signature of user delegation SAS test methods + */ + public Arguments toArguments(boolean withHeadersAndParams) { + if (withHeadersAndParams) { + return Arguments.of(startTime, keyOid, keyTid, keyStart, keyExpiry, keyService, keyVersion, keyValue, + ipRange, protocol, cacheControl, disposition, encoding, language, type, requestHeaders, + requestQueryParameters, saoid, suoid, cid, expectedStringToSign); + } else { + return Arguments.of(startTime, keyOid, keyTid, keyStart, keyExpiry, keyService, keyVersion, keyValue, + ipRange, protocol, cacheControl, disposition, encoding, language, type, saoid, suoid, cid, expectedStringToSign); + } + } + + /** + * Converts to Arguments for user delegation SAS tests with request headers and query parameters. + * This is a convenience method that calls {@link #toArguments(boolean)} with true. + * + * @return Arguments for parameterized tests with headers and query parameters included + */ + public Arguments toArguments() { + return toArguments(true); + } +} + + diff --git a/sdk/storage/azure-storage-common/src/test/java/com/azure/storage/common/implementation/SasImplUtilsTests.java b/sdk/storage/azure-storage-common/src/test/java/com/azure/storage/common/implementation/SasImplUtilsTests.java index c69b9457f693..919572bb8bdb 100644 --- a/sdk/storage/azure-storage-common/src/test/java/com/azure/storage/common/implementation/SasImplUtilsTests.java +++ b/sdk/storage/azure-storage-common/src/test/java/com/azure/storage/common/implementation/SasImplUtilsTests.java @@ -14,11 +14,13 @@ public class SasImplUtilsTests { - private static Map requestHeaders; + private Map requestHeaders; + private Map requestQueryParams; @BeforeEach public void setup() { requestHeaders = new HashMap<>(); + requestQueryParams = new HashMap<>(); } @Test @@ -31,6 +33,15 @@ public void formatRequestHeadersForSasSigningEmptyReturnsEmptyString() { assertEquals("", SasImplUtils.formatRequestHeadersForSasSigning(requestHeaders)); } + @Test + public void formatRequestHeadersForSasSigningReturnsWithLastCharAsNewline() { + requestHeaders.put("Some-Header", "someValue"); + String headerString = SasImplUtils.formatRequestHeadersForSasSigning(requestHeaders); + + assertNotEquals("", headerString); + assertEquals("\n", headerString.substring(headerString.length() - 1)); + } + @Test public void formatRequestHeadersForSasSigningPopulatedHeaders() { requestHeaders.put(Constants.HeaderConstants.ENCRYPTION_KEY, "encryptionKeyValue"); @@ -53,4 +64,43 @@ public void formatRequestHeadersForSasSigningPopulatedHeaders() { assertEquals(4, newLineCount); assertEquals(sortedExpected, sortedHeaders); } + + @Test + public void formatRequestQueryParamsForSasSigningNullReturnsEmptyString() { + assertEquals("", SasImplUtils.formatRequestQueryParametersForSasSigning(null)); + } + + @Test + public void formatRequestQueryParamsForSasSigningEmptyReturnsEmptyString() { + assertEquals("", SasImplUtils.formatRequestQueryParametersForSasSigning(requestQueryParams)); + } + + @Test + public void formatRequestQueryParamsForSasSigningReturnsWithFirstCharAsNewline() { + requestQueryParams.put("someParam", "someValue"); + + String queryParamString = SasImplUtils.formatRequestQueryParametersForSasSigning(requestQueryParams); + + assertNotEquals("", queryParamString); + assertEquals("\n", queryParamString.substring(0, 1)); + } + + @Test + public void formatRequestQueryParamsForSasSigningPopulatedParams() { + requestQueryParams.put("paramA", "valueA"); + requestQueryParams.put("paramB", "valueB"); + requestQueryParams.put("paramC", "valueC"); + String expected = "\nparamA:valueA\nparamB:valueB\nparamC:valueC"; + + String queryParams = SasImplUtils.formatRequestQueryParametersForSasSigning(requestQueryParams); + Integer newLineCount + = Arrays.stream(queryParams.split("")).filter(s -> s.equals("\n")).collect(Collectors.toList()).size(); + String sortedExpected + = "\n" + Arrays.stream(expected.substring(1).split("\n")).sorted().collect(Collectors.joining("\n")); + String sortedQueryParams + = "\n" + Arrays.stream(queryParams.substring(1).split("\n")).sorted().collect(Collectors.joining("\n")); + + assertEquals(3, newLineCount); + assertEquals(sortedExpected, sortedQueryParams); + } } diff --git a/sdk/storage/azure-storage-file-datalake/assets.json b/sdk/storage/azure-storage-file-datalake/assets.json index 90e1f30a59e7..f5bb8d4f9891 100644 --- a/sdk/storage/azure-storage-file-datalake/assets.json +++ b/sdk/storage/azure-storage-file-datalake/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/storage/azure-storage-file-datalake", - "Tag": "java/storage/azure-storage-file-datalake_e7c65c4771" + "Tag": "java/storage/azure-storage-file-datalake_cc5d8d21d2" } diff --git a/sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/SasTests.java b/sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/SasTests.java index 9e6979070c7c..8984681b6388 100644 --- a/sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/SasTests.java +++ b/sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/SasTests.java @@ -14,6 +14,8 @@ 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.SasTestData; +import com.azure.storage.common.test.shared.UserDelegationSasTestData; import com.azure.storage.common.test.shared.extensions.LiveOnly; import com.azure.storage.common.test.shared.extensions.RequiredServiceVersion; import com.azure.storage.file.datalake.implementation.util.DataLakeSasImplUtil; @@ -41,8 +43,11 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.stream.Stream; import static com.azure.storage.common.test.shared.StorageCommonTestUtils.getOidFromToken; @@ -775,62 +780,45 @@ private static Stream sasImplUtilStringToSignSupplier() { // /blob/accountName. We test canonicalization of resources later. Again, this is not to test a fully functional // sas but the construction of the string to sign. // Signed resource is tested elsewhere, as we work some minor magic in choosing which value to use. - return Stream.of( - // startTime | identifier | ipRange | protocol | cacheControl | disposition | encoding | language | type | expectedStringToSign - Arguments.of(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), 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)) - + "\n" - + Constants.ISO_8601_UTC_DATE_FORMATTER - .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\n\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\n\n\n\n\n\n"), - Arguments.of(null, "id", 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/fileSystemName/pathName\nid\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), - Arguments.of(null, null, new SasIpRange(), 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/fileSystemName/pathName\n\nip\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n"), - Arguments.of(null, null, null, SasProtocol.HTTPS_ONLY, 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/fileSystemName/pathName\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, "control", 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/fileSystemName/pathName\n\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\n\ncontrol\n\n\n\n"), - Arguments.of(null, null, null, null, null, "disposition", 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/fileSystemName/pathName\n\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\n\n\ndisposition\n\n\n"), - Arguments.of(null, null, null, null, null, null, "encoding", 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/fileSystemName/pathName\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, "language", 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/fileSystemName/pathName\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, null, "type", - "r\n\n" - + Constants.ISO_8601_UTC_DATE_FORMATTER - .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\n\n\n\n" + Constants.SAS_SERVICE_VERSION - + "\nb\n\n\n\n\n\n\ntype")); + OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + String expiryTimeStr = Constants.ISO_8601_UTC_DATE_FORMATTER.format(expiryTime); + + return Stream.of(new SasTestData().setStartTime(expiryTime) + .setExpectedStringToSign("r\n" + expiryTimeStr + "\n" + expiryTimeStr + + "\n/blob/%s/fileSystemName/pathName\n\n\n\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n") + .toArguments(), + new SasTestData().setIdentifier("id") + .setExpectedStringToSign("r\n\n" + expiryTimeStr + "\n/blob/%s/fileSystemName/pathName\nid\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n") + .toArguments(), + new SasTestData().setIpRange(new SasIpRange()) + .setExpectedStringToSign("r\n\n" + expiryTimeStr + "\n/blob/%s/fileSystemName/pathName\n\nip\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n") + .toArguments(), + new SasTestData().setProtocol(SasProtocol.HTTPS_ONLY) + .setExpectedStringToSign("r\n\n" + expiryTimeStr + "\n/blob/%s/fileSystemName/pathName\n\n\n" + + SasProtocol.HTTPS_ONLY + "\n" + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n") + .toArguments(), + new SasTestData().setCacheControl("control") + .setExpectedStringToSign("r\n\n" + expiryTimeStr + "\n/blob/%s/fileSystemName/pathName\n\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\ncontrol\n\n\n\n") + .toArguments(), + new SasTestData().setDisposition("disposition") + .setExpectedStringToSign("r\n\n" + expiryTimeStr + "\n/blob/%s/fileSystemName/pathName\n\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\ndisposition\n\n\n") + .toArguments(), + new SasTestData().setEncoding("encoding") + .setExpectedStringToSign("r\n\n" + expiryTimeStr + "\n/blob/%s/fileSystemName/pathName\n\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\nencoding\n\n") + .toArguments(), + new SasTestData().setLanguage("language") + .setExpectedStringToSign("r\n\n" + expiryTimeStr + "\n/blob/%s/fileSystemName/pathName\n\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\nlanguage\n") + .toArguments(), + new SasTestData().setType("type") + .setExpectedStringToSign("r\n\n" + expiryTimeStr + "\n/blob/%s/fileSystemName/pathName\n\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\ntype") + .toArguments()); } @RequiredServiceVersion(clazz = DataLakeServiceVersion.class, min = "2020-12-06") @@ -839,10 +827,12 @@ private static Stream sasImplUtilStringToSignSupplier() { public void sasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTime, String keyOid, String keyTid, OffsetDateTime keyStart, OffsetDateTime keyExpiry, String keyService, String keyVersion, String keyValue, SasIpRange ipRange, SasProtocol protocol, String cacheControl, String disposition, String encoding, - String language, String type, String saoid, String suoid, String cid, String expectedStringToSign) { + String language, String type, Map requestHeaders, Map requestQueryParameters, + String saoid, String suoid, String cid, String expectedStringToSign) { OffsetDateTime e = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); PathSasPermission p = new PathSasPermission().setReadPermission(true); + ArrayList stringToSign = new ArrayList<>(); String expected = String.format(expectedStringToSign, ENVIRONMENT.getDataLakeAccount().getName()); DataLakeServiceSasSignatureValues v = new DataLakeServiceSasSignatureValues(e, p).setPermissions(p) @@ -856,7 +846,9 @@ public void sasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTime, S .setContentType(type) .setCorrelationId(cid) .setPreauthorizedAgentObjectId(saoid) - .setAgentObjectId(suoid); + .setAgentObjectId(suoid) + .setRequestHeaders(requestHeaders) + .setRequestQueryParameters(requestQueryParameters); if (ipRange != null) { v.setSasIpRange(new SasIpRange().setIpMin("ip")); @@ -872,150 +864,183 @@ public void sasImplUtilStringToSignUserDelegationKey(OffsetDateTime startTime, S DataLakeSasImplUtil util = new DataLakeSasImplUtil(v, "fileSystemName", "pathName", false); util.ensureState(); + String sasToken = util.generateUserDelegationSas(key, ENVIRONMENT.getDataLakeAccount().getName(), + stringToSign::add, Context.NONE); + assertEqualsForEachLine(stringToSign, expected); assertEquals(expected, util.stringToSign(key, util.getCanonicalName(ENVIRONMENT.getDataLakeAccount().getName()))); } private static Stream sasImplUtilStringToSignUserDelegationKeySupplier() { + // Use LinkedHashMap to ensure deterministic iteration order + Map singleHeader = new LinkedHashMap<>(); + singleHeader.put("x-ms-encryption-key-sha256", "hashvalue"); + + Map singleQueryParam = new LinkedHashMap<>(); + singleQueryParam.put("comp", "blocklist"); + + Map multipleHeaders = new LinkedHashMap<>(); + multipleHeaders.put("x-ms-encryption-key-sha256", "hashvalue"); + multipleHeaders.put("x-ms-source-if-match", "etag"); + + Map multipleQueryParams = new LinkedHashMap<>(); + multipleQueryParams.put("blockid", "blockidvalue"); + multipleQueryParams.put("comp", "blocklist"); + // We test string to sign functionality directly related to user delegation sas specific parameters return Stream.of( - // startTime | keyOid | keyTid | keyStart | keyExpiry | keyService | keyVersion | keyValue | ipRange | protocol | cacheControl | disposition | encoding | language | type | saoid | suoid | cid | expectedStringToSign - Arguments.of(null, null, null, null, null, null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, - null, null, null, null, null, null, null, null, null, - "r\n\n" + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\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\n"), - 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, - "r\n" + + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setStartTime(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + .setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setExpectedStringToSign("r\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\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\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, - "r\n\n" + + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyOid("11111111-1111-1111-1111-111111111111") + .setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\n11111111-1111-1111-1111-111111111111\n\n\n\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, "22222222-2222-2222-2222-222222222222", null, null, null, null, - "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, - "r\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyTid("22222222-2222-2222-2222-222222222222") + .setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\n\n22222222-2222-2222-2222-222222222222\n\n\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, 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, - "r\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData() + .setKeyStart(OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC)) + .setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\n\n\n2018-01-01T00:00:00Z\n\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, OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC), - null, null, "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, - null, null, null, - "r\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData() + .setKeyExpiry(OffsetDateTime.of(LocalDateTime.of(2018, 1, 1, 0, 0), ZoneOffset.UTC)) + .setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\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, - "r\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyService("b") + .setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\n\n\n\n\nb\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, null, "2018-06-17", - "3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=", null, null, null, null, null, null, null, null, null, - null, - "r\n\n" + + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyVersion("2018-06-17") + .setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\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, - "r\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setIpRange(new SasIpRange()) + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\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, - "r\n\n" + + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setEncoding("encoding") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\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, "control", null, null, null, null, null, null, null, - "r\n\n" + + "\n/blob/%s/fileSystemName/pathName\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\nencoding\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setType("type") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\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, "disposition", null, null, null, null, null, null, - "r\n\n" + + "\nb\n\n\n\n\n\n\n\n\ntype") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setSaoid("saoid") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\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, "encoding", null, null, null, null, null, - "r\n\n" + + "\n/blob/%s/fileSystemName/pathName\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\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setSuoid("suoid") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\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, "language", null, null, null, null, - "r\n\n" + + "\n/blob/%s/fileSystemName/pathName\n\n\n\n\n\n\n\nsuoid\n\n\n\n\n\n" + + Constants.SAS_SERVICE_VERSION + "\nb\n\n\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setCid("cid") + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\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, "type", null, null, null, - "r\n\n" + + "\n/blob/%s/fileSystemName/pathName\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\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setRequestHeaders(singleHeader) + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) + "\n/blob/%s/fileSystemName/pathName\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, "saoid", null, null, - "r\n\n" + + "\nb\n\n\nx-ms-encryption-key-sha256:hashvalue\n\n\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setRequestQueryParameters(singleQueryParam) + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\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, "suoid", null, - "r\n\n" + + "\n/blob/%s/fileSystemName/pathName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + + "\nb\n\n\n\n\ncomp:blocklist\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setRequestHeaders(singleHeader) + .setRequestQueryParameters(singleQueryParam) + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\n\n\n\n\n\n\n\nsuoid\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, "cid", - "r\n\n" + + "\n/blob/%s/fileSystemName/pathName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + + "\nb\n\n\nx-ms-encryption-key-sha256:hashvalue\n\n\ncomp:blocklist\n\n\n\n\n") + .toArguments(), + new UserDelegationSasTestData().setKeyValue("3hd4LRwrARVGbeMRQRfTLIsGMkCPuZJnvxZDU7Gak8c=") + .setRequestHeaders(multipleHeaders) + .setRequestQueryParameters(multipleQueryParams) + .setExpectedStringToSign("r\n\n" + Constants.ISO_8601_UTC_DATE_FORMATTER .format(OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)) - + "\n/blob/%s/fileSystemName/pathName\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")); + + "\n/blob/%s/fileSystemName/pathName\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + Constants.SAS_SERVICE_VERSION + + "\nb\n\n\nx-ms-encryption-key-sha256:hashvalue\n" + + "x-ms-source-if-match:etag\n\n\nblockid:blockidvalue\n" + "comp:blocklist\n\n\n\n\n") + .toArguments()); } @Test @@ -1085,4 +1110,35 @@ public void canUseSasToAuthenticate() { .getProperties()); } + private static void assertEqualsForEachLine(ArrayList stringToSign, String expected) { + String actual = stringToSign.get(0); + if (!expected.equals(actual)) { + StringBuilder output = new StringBuilder(); + String[] expectedLines = expected.split("\n", -1); + String[] actualLines = actual.split("\n", -1); + + output.append("\n=== Line-by-Line String-to-Sign Comparison ===\n"); + output.append("Expected lines: ").append(expectedLines.length).append("\n"); + output.append("Actual lines: ").append(actualLines.length).append("\n\n"); + + int maxLines = Math.max(expectedLines.length, actualLines.length); + for (int i = 0; i < maxLines; i++) { + String expLine = i < expectedLines.length ? expectedLines[i] : ""; + String actLine = i < actualLines.length ? actualLines[i] : ""; + + if (!expLine.equals(actLine)) { + output.append("Line ").append(i).append(" differs:\n"); + output.append(" Expected: [").append(expLine).append("]\n"); + output.append(" Actual: [").append(actLine).append("]\n\n"); + } else { + output.append("Line ").append(i).append(" matches: [").append(expLine).append("]\n"); + } + } + + // Print everything at once + System.out.println(output.toString()); + } + assertEquals(expected, actual, "String-to-sign mismatch"); + } + }