Skip to content

Commit 012d629

Browse files
authored
Merge pull request #2 from browndav-msft/stg101/dynamicSas
Stg101/dynamic sas
2 parents ec472d8 + f4ae369 commit 012d629

8 files changed

Lines changed: 759 additions & 191 deletions

File tree

sdk/storage/azure-storage-blob/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "java",
44
"TagPrefix": "java/storage/azure-storage-blob",
5-
"Tag": "java/storage/azure-storage-blob_f26563826e"
5+
"Tag": "java/storage/azure-storage-blob_6631ad464e"
66
}

sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/SasClientTests.java

Lines changed: 222 additions & 44 deletions
Large diffs are not rendered by default.

sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/SasImplUtils.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ public static Map<String, String[]> parseQueryString(String queryParams) {
119119
return retVals;
120120
}
121121

122+
/**
123+
* Formats request headers for SAS signing.
124+
*
125+
* @param requestHeaders The map of request headers to format.
126+
* @return A formatted string with headers in the format "key:value" separated by newlines, or empty string if
127+
* null/empty. Terminates each pair with a newline (\n).
128+
* @see
129+
* <a href="https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas#version-2026-04-06-and-later-blob-storage-and-data-lake-storage">
130+
* Version 2026-04-06 and later (Blob Storage and Data Lake Storage)</a>
131+
*/
132+
122133
public static String formatRequestHeadersForSasSigning(Map<String, String> requestHeaders) {
123134
if (requestHeaders == null || requestHeaders.isEmpty()) {
124135
return "";
@@ -128,6 +139,16 @@ public static String formatRequestHeadersForSasSigning(Map<String, String> reque
128139
return sb.toString();
129140
}
130141

142+
/**
143+
* Formats request headers for SAS signing.
144+
*
145+
* @param requestQueryParameters The map of request headers to format.
146+
* @return A formatted string with query params in the format "key:value" separated by newlines, or empty string if
147+
* null/empty. Prepends a newline character. Prefixes each pair with a newline (\n).
148+
* @see
149+
* <a href="https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas#version-2026-04-06-and-later-blob-storage-and-data-lake-storage">
150+
* Version 2026-04-06 and later (Blob Storage and Data Lake Storage)</a>
151+
*/
131152
public static String formatRequestQueryParametersForSasSigning(Map<String, String> requestQueryParameters) {
132153
if (requestQueryParameters == null || requestQueryParameters.isEmpty()) {
133154
return "";
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.azure.storage.common.test.shared;
4+
import com.azure.storage.common.sas.SasIpRange;
5+
import com.azure.storage.common.sas.SasProtocol;
6+
import org.junit.jupiter.params.provider.Arguments;
7+
import java.time.OffsetDateTime;
8+
/**
9+
* Helper class to build test arguments for regular SAS string-to-sign tests.
10+
* This is the base class that contains common fields shared by both regular SAS and user delegation SAS.
11+
* All fields default to null, so you only need to set the ones you're testing.
12+
* <p>
13+
* For user delegation SAS tests, use {@link UserDelegationSasTestData} which extends this class.
14+
*/
15+
public class SasTestData {
16+
// Common fields for all SAS types
17+
protected OffsetDateTime startTime;
18+
protected SasIpRange ipRange;
19+
protected SasProtocol protocol;
20+
protected String cacheControl;
21+
protected String disposition;
22+
protected String encoding;
23+
protected String language;
24+
protected String type;
25+
protected String expectedStringToSign;
26+
// Regular SAS specific field
27+
protected String identifier; // Signed identifier for regular SAS
28+
public SasTestData setStartTime(OffsetDateTime startTime) {
29+
this.startTime = startTime;
30+
return this;
31+
}
32+
public SasTestData setIdentifier(String identifier) {
33+
this.identifier = identifier;
34+
return this;
35+
}
36+
public SasTestData setIpRange(SasIpRange ipRange) {
37+
this.ipRange = ipRange;
38+
return this;
39+
}
40+
public SasTestData setProtocol(SasProtocol protocol) {
41+
this.protocol = protocol;
42+
return this;
43+
}
44+
public SasTestData setCacheControl(String cacheControl) {
45+
this.cacheControl = cacheControl;
46+
return this;
47+
}
48+
public SasTestData setDisposition(String disposition) {
49+
this.disposition = disposition;
50+
return this;
51+
}
52+
public SasTestData setEncoding(String encoding) {
53+
this.encoding = encoding;
54+
return this;
55+
}
56+
public SasTestData setLanguage(String language) {
57+
this.language = language;
58+
return this;
59+
}
60+
public SasTestData setType(String type) {
61+
this.type = type;
62+
return this;
63+
}
64+
public SasTestData setExpectedStringToSign(String expectedStringToSign) {
65+
this.expectedStringToSign = expectedStringToSign;
66+
return this;
67+
}
68+
/**
69+
* Converts to Arguments for regular SAS tests.
70+
* Returns arguments in this order:
71+
* startTime, identifier, ipRange, protocol, cacheControl, disposition, encoding, language, type, expectedStringToSign
72+
*
73+
* @return Arguments for parameterized tests matching the signature of regular SAS test methods
74+
*/
75+
public Arguments toArguments() {
76+
return Arguments.of(startTime, identifier, ipRange, protocol, cacheControl, disposition, encoding, language,
77+
type, expectedStringToSign);
78+
}
79+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.storage.common.test.shared;
5+
6+
import com.azure.storage.common.sas.SasIpRange;
7+
import com.azure.storage.common.sas.SasProtocol;
8+
import org.junit.jupiter.params.provider.Arguments;
9+
10+
import java.time.OffsetDateTime;
11+
import java.util.Map;
12+
13+
/**
14+
* Helper class to build test arguments for User Delegation SAS string-to-sign tests.
15+
* Extends {@link SasTestData} to inherit common SAS fields.
16+
* All fields default to null, so you only need to set the ones you're testing.
17+
* <p>
18+
* Note: User delegation SAS does NOT use the 'identifier' field (that's for regular SAS).
19+
* Request headers and query parameters are only used in user delegation SAS.
20+
* <p>
21+
* For regular SAS tests, use {@link SasTestData} directly.
22+
*/
23+
public class UserDelegationSasTestData extends SasTestData {
24+
// User delegation SAS specific fields
25+
private String keyOid;
26+
private String keyTid;
27+
private OffsetDateTime keyStart;
28+
private OffsetDateTime keyExpiry;
29+
private String keyService;
30+
private String keyVersion;
31+
private String keyValue;
32+
private Map<String, String> requestHeaders;
33+
private Map<String, String> requestQueryParameters;
34+
private String saoid;
35+
private String suoid;
36+
private String cid;
37+
38+
// Override parent setters to return UserDelegationSasTestData for fluent API
39+
@Override
40+
public UserDelegationSasTestData setStartTime(OffsetDateTime startTime) {
41+
super.setStartTime(startTime);
42+
return this;
43+
}
44+
45+
@Override
46+
public UserDelegationSasTestData setIpRange(SasIpRange ipRange) {
47+
super.setIpRange(ipRange);
48+
return this;
49+
}
50+
51+
@Override
52+
public UserDelegationSasTestData setProtocol(SasProtocol protocol) {
53+
super.setProtocol(protocol);
54+
return this;
55+
}
56+
57+
@Override
58+
public UserDelegationSasTestData setCacheControl(String cacheControl) {
59+
super.setCacheControl(cacheControl);
60+
return this;
61+
}
62+
63+
@Override
64+
public UserDelegationSasTestData setDisposition(String disposition) {
65+
super.setDisposition(disposition);
66+
return this;
67+
}
68+
69+
@Override
70+
public UserDelegationSasTestData setEncoding(String encoding) {
71+
super.setEncoding(encoding);
72+
return this;
73+
}
74+
75+
@Override
76+
public UserDelegationSasTestData setLanguage(String language) {
77+
super.setLanguage(language);
78+
return this;
79+
}
80+
81+
@Override
82+
public UserDelegationSasTestData setType(String type) {
83+
super.setType(type);
84+
return this;
85+
}
86+
87+
@Override
88+
public UserDelegationSasTestData setExpectedStringToSign(String expectedStringToSign) {
89+
super.setExpectedStringToSign(expectedStringToSign);
90+
return this;
91+
}
92+
93+
// User delegation SAS specific setters
94+
95+
public UserDelegationSasTestData setKeyOid(String keyOid) {
96+
this.keyOid = keyOid;
97+
return this;
98+
}
99+
100+
public UserDelegationSasTestData setKeyTid(String keyTid) {
101+
this.keyTid = keyTid;
102+
return this;
103+
}
104+
105+
public UserDelegationSasTestData setKeyStart(OffsetDateTime keyStart) {
106+
this.keyStart = keyStart;
107+
return this;
108+
}
109+
110+
public UserDelegationSasTestData setKeyExpiry(OffsetDateTime keyExpiry) {
111+
this.keyExpiry = keyExpiry;
112+
return this;
113+
}
114+
115+
public UserDelegationSasTestData setKeyService(String keyService) {
116+
this.keyService = keyService;
117+
return this;
118+
}
119+
120+
public UserDelegationSasTestData setKeyVersion(String keyVersion) {
121+
this.keyVersion = keyVersion;
122+
return this;
123+
}
124+
125+
public UserDelegationSasTestData setKeyValue(String keyValue) {
126+
this.keyValue = keyValue;
127+
return this;
128+
}
129+
130+
public UserDelegationSasTestData setRequestHeaders(Map<String, String> requestHeaders) {
131+
this.requestHeaders = requestHeaders;
132+
return this;
133+
}
134+
135+
public UserDelegationSasTestData setRequestQueryParameters(Map<String, String> requestQueryParameters) {
136+
this.requestQueryParameters = requestQueryParameters;
137+
return this;
138+
}
139+
140+
public UserDelegationSasTestData setSaoid(String saoid) {
141+
this.saoid = saoid;
142+
return this;
143+
}
144+
145+
public UserDelegationSasTestData setSuoid(String suoid) {
146+
this.suoid = suoid;
147+
return this;
148+
}
149+
150+
public UserDelegationSasTestData setCid(String cid) {
151+
this.cid = cid;
152+
return this;
153+
}
154+
155+
/**
156+
* Converts to Arguments for user delegation SAS tests.
157+
* Returns arguments with or without request headers/query parameters based on the parameter.
158+
*
159+
* @param withHeadersAndParams Whether to include request headers and query parameters in the test data.
160+
* @return Arguments for parameterized tests matching the signature of user delegation SAS test methods
161+
*/
162+
public Arguments toArguments(boolean withHeadersAndParams) {
163+
if (withHeadersAndParams) {
164+
return Arguments.of(startTime, keyOid, keyTid, keyStart, keyExpiry, keyService, keyVersion, keyValue,
165+
ipRange, protocol, cacheControl, disposition, encoding, language, type, requestHeaders,
166+
requestQueryParameters, saoid, suoid, cid, expectedStringToSign);
167+
} else {
168+
return Arguments.of(startTime, keyOid, keyTid, keyStart, keyExpiry, keyService, keyVersion, keyValue,
169+
ipRange, protocol, cacheControl, disposition, encoding, language, type, saoid, suoid, cid, expectedStringToSign);
170+
}
171+
}
172+
173+
/**
174+
* Converts to Arguments for user delegation SAS tests with request headers and query parameters.
175+
* This is a convenience method that calls {@link #toArguments(boolean)} with true.
176+
*
177+
* @return Arguments for parameterized tests with headers and query parameters included
178+
*/
179+
public Arguments toArguments() {
180+
return toArguments(true);
181+
}
182+
}
183+
184+

sdk/storage/azure-storage-common/src/test/java/com/azure/storage/common/implementation/SasImplUtilsTests.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414

1515
public class SasImplUtilsTests {
1616

17-
private static Map<String, String> requestHeaders;
17+
private Map<String, String> requestHeaders;
18+
private Map<String, String> requestQueryParams;
1819

1920
@BeforeEach
2021
public void setup() {
2122
requestHeaders = new HashMap<>();
23+
requestQueryParams = new HashMap<>();
2224
}
2325

2426
@Test
@@ -31,6 +33,15 @@ public void formatRequestHeadersForSasSigningEmptyReturnsEmptyString() {
3133
assertEquals("", SasImplUtils.formatRequestHeadersForSasSigning(requestHeaders));
3234
}
3335

36+
@Test
37+
public void formatRequestHeadersForSasSigningReturnsWithLastCharAsNewline() {
38+
requestHeaders.put("Some-Header", "someValue");
39+
String headerString = SasImplUtils.formatRequestHeadersForSasSigning(requestHeaders);
40+
41+
assertNotEquals("", headerString);
42+
assertEquals("\n", headerString.substring(headerString.length() - 1));
43+
}
44+
3445
@Test
3546
public void formatRequestHeadersForSasSigningPopulatedHeaders() {
3647
requestHeaders.put(Constants.HeaderConstants.ENCRYPTION_KEY, "encryptionKeyValue");
@@ -53,4 +64,43 @@ public void formatRequestHeadersForSasSigningPopulatedHeaders() {
5364
assertEquals(4, newLineCount);
5465
assertEquals(sortedExpected, sortedHeaders);
5566
}
67+
68+
@Test
69+
public void formatRequestQueryParamsForSasSigningNullReturnsEmptyString() {
70+
assertEquals("", SasImplUtils.formatRequestQueryParametersForSasSigning(null));
71+
}
72+
73+
@Test
74+
public void formatRequestQueryParamsForSasSigningEmptyReturnsEmptyString() {
75+
assertEquals("", SasImplUtils.formatRequestQueryParametersForSasSigning(requestQueryParams));
76+
}
77+
78+
@Test
79+
public void formatRequestQueryParamsForSasSigningReturnsWithFirstCharAsNewline() {
80+
requestQueryParams.put("someParam", "someValue");
81+
82+
String queryParamString = SasImplUtils.formatRequestQueryParametersForSasSigning(requestQueryParams);
83+
84+
assertNotEquals("", queryParamString);
85+
assertEquals("\n", queryParamString.substring(0, 1));
86+
}
87+
88+
@Test
89+
public void formatRequestQueryParamsForSasSigningPopulatedParams() {
90+
requestQueryParams.put("paramA", "valueA");
91+
requestQueryParams.put("paramB", "valueB");
92+
requestQueryParams.put("paramC", "valueC");
93+
String expected = "\nparamA:valueA\nparamB:valueB\nparamC:valueC";
94+
95+
String queryParams = SasImplUtils.formatRequestQueryParametersForSasSigning(requestQueryParams);
96+
Integer newLineCount
97+
= Arrays.stream(queryParams.split("")).filter(s -> s.equals("\n")).collect(Collectors.toList()).size();
98+
String sortedExpected
99+
= "\n" + Arrays.stream(expected.substring(1).split("\n")).sorted().collect(Collectors.joining("\n"));
100+
String sortedQueryParams
101+
= "\n" + Arrays.stream(queryParams.substring(1).split("\n")).sorted().collect(Collectors.joining("\n"));
102+
103+
assertEquals(3, newLineCount);
104+
assertEquals(sortedExpected, sortedQueryParams);
105+
}
56106
}

sdk/storage/azure-storage-file-datalake/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "java",
44
"TagPrefix": "java/storage/azure-storage-file-datalake",
5-
"Tag": "java/storage/azure-storage-file-datalake_e7c65c4771"
5+
"Tag": "java/storage/azure-storage-file-datalake_cc5d8d21d2"
66
}

0 commit comments

Comments
 (0)