Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7edd303
wip implementation
ibrandes Dec 7, 2025
5520741
add base tests for SasImplUtilsTests
browndav-msft Jan 20, 2026
833e3e8
add base tests for SasImplUtilsTests
browndav-msft Jan 20, 2026
2c731da
revert changes to stringToSign from changes made based on api
browndav-msft Jan 27, 2026
863b7b6
remove static from SasImplUtilsTests so that beforeeach works
browndav-msft Jan 27, 2026
28246fb
add documentation for srq and srh
browndav-msft Jan 27, 2026
05b118e
finish sasClientTests
browndav-msft Jan 28, 2026
dd2a7fe
uncomment stream of args from blobSasImplUtilStringToSignUserDelegati…
browndav-msft Jan 28, 2026
beb0f3d
fix linting issue
browndav-msft Jan 28, 2026
bb87838
combine test for srh and srq into blobSasImplUtilStringToSignUserDele…
browndav-msft Jan 28, 2026
42ad7c5
add recordings for blobSasImplUtilStringToSignUserDelegationKey
browndav-msft Jan 28, 2026
d54b8c2
add additional srh test to check for trailing newline
browndav-msft Jan 28, 2026
4e8a539
add tests for srq
browndav-msft Jan 28, 2026
8559364
add tests for srq and srh to datalake
browndav-msft Jan 29, 2026
88a793a
fix failing datalake sas tests
browndav-msft Jan 29, 2026
47ef9c7
add recordings for blobSasImplUtilStringToSignUserDelegationKey
browndav-msft Jan 29, 2026
e06736e
refactor SasTests to make the suppliers easier to read and write
browndav-msft Jan 30, 2026
7c8b617
making new values in commonsasqueryparameters final
ibrandes Feb 2, 2026
5676ff3
adding note to new sas query param javadocs that they're only for UD sas
ibrandes Feb 2, 2026
372cf05
adding note to datalake sas params too
ibrandes Feb 2, 2026
cee3628
Merge remote-tracking branch 'isabelle/stg101/dynamicSas' into stg101…
browndav-msft Feb 2, 2026
4a20e60
add header to sasimplutiltests
browndav-msft Feb 2, 2026
ec472d8
applying spotless
ibrandes Feb 2, 2026
f4ae369
Merge branch 'stg101/dynamicSas' into stg101/dynamicSas
ibrandes Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-blob/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
import com.azure.storage.common.sas.SasProtocol;

import java.time.OffsetDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

import static com.azure.storage.common.implementation.SasImplUtils.formatQueryParameterDate;
import static com.azure.storage.common.implementation.SasImplUtils.formatRequestHeadersForSasSigning;
import static com.azure.storage.common.implementation.SasImplUtils.formatRequestQueryParametersForSasSigning;
import static com.azure.storage.common.implementation.SasImplUtils.tryAppendQueryParameter;

/**
* This class provides helper methods for common blob service sas patterns.
*
* RESERVED FOR INTERNAL USE.
*/
public class BlobSasImplUtil {
Expand Down Expand Up @@ -58,44 +60,27 @@ public class BlobSasImplUtil {
.get(Constants.PROPERTY_AZURE_STORAGE_SAS_SERVICE_VERSION, BlobServiceVersion.getLatest().getVersion());

private SasProtocol protocol;

private OffsetDateTime startTime;

private OffsetDateTime expiryTime;

private String permissions;

private SasIpRange sasIpRange;

private String containerName;

private String blobName;

private String resource;

private String snapshotId;

private String versionId;

private String identifier;

private String cacheControl;

private String contentDisposition;

private String contentEncoding;

private String contentLanguage;

private String contentType;

private String authorizedAadObjectId;

private String correlationId;

private String encryptionScope;

private String delegatedUserObjectId;
private Map<String, String> requestHeaders;
private Map<String, String> requestQueryParameters;

/**
* Creates a new {@link BlobSasImplUtil} with the specified parameters
Expand Down Expand Up @@ -143,6 +128,8 @@ public BlobSasImplUtil(BlobServiceSasSignatureValues sasValues, String container
this.correlationId = sasValues.getCorrelationId();
this.encryptionScope = encryptionScope;
this.delegatedUserObjectId = sasValues.getDelegatedUserObjectId();
this.requestHeaders = sasValues.getRequestHeaders();
this.requestQueryParameters = sasValues.getRequestQueryParameters();
}

/**
Expand Down Expand Up @@ -272,31 +259,34 @@ private String encode(UserDelegationKey userDelegationKey, String signature) {
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions);
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_SIGNATURE, signature);
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_ENCRYPTION_SCOPE, this.encryptionScope);
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_REQUEST_HEADERS,
formatRequestHeadersForSasSigning(this.requestHeaders));
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_REQUEST_QUERY_PARAMETERS,
formatRequestQueryParametersForSasSigning(this.requestQueryParameters));
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CACHE_CONTROL, this.cacheControl);
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CONTENT_DISPOSITION, this.contentDisposition);
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CONTENT_ENCODING, this.contentEncoding);
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CONTENT_LANGUAGE, this.contentLanguage);
tryAppendQueryParameter(sb, Constants.UrlConstants.SAS_CONTENT_TYPE, this.contentType);

return sb.toString();

}

/**
* Ensures that the builder's properties are in a consistent state.
*
* 1. If there is no version, use latest.
* <p> 1. If there is no version, use latest.
* 2. If there is no identifier set, ensure expiryTime and permissions are set.
* 3. Resource name is chosen by:
* a. If "BlobName" is _not_ set, it is a container resource.
* b. Otherwise, if "SnapshotId" is set, it is a blob snapshot resource.
* c. Otherwise, if "VersionId" is set, it is a blob version resource.
* d. Otherwise, it is a blob resource.
* 4. Reparse permissions depending on what the resource is. If it is an unrecognized resource, do nothing.
* 4. Reparse permissions depending on what the resource is. If it is an unrecognized resource, do nothing. </p>
*
* Taken from:
* https://github.com/Azure/azure-storage-blob-go/blob/master/azblob/sas_service.go#L33
* https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/storage/Azure.Storage.Blobs/src/Sas/BlobSasBuilder.cs
* <a href="https://github.com/Azure/azure-storage-blob-go/blob/master/azblob/sas_service.go#L33">sas_service.go</a>
* <a href="https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/storage/Azure.Storage.Blobs/src/Sas/BlobSasBuilder.cs">BlobSasBuilder.cs</a>
*/
public void ensureState() {
if (identifier == null) {
Expand Down Expand Up @@ -443,6 +433,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 if (VERSION.compareTo(BlobServiceVersion.V2026_02_06.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),
Expand All @@ -462,6 +476,10 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) {
this.sasIpRange == null ? "" : this.sasIpRange.toString(),
this.protocol == null ? "" : this.protocol.toString(), VERSION, resource,
versionSegment == null ? "" : versionSegment, this.encryptionScope == null ? "" : this.encryptionScope,
this.requestHeaders == null ? "" : formatRequestHeadersForSasSigning(this.requestHeaders),
this.requestQueryParameters == null
? ""
: formatRequestQueryParametersForSasSigning(this.requestQueryParameters),
this.cacheControl == null ? "" : this.cacheControl,
this.contentDisposition == null ? "" : this.contentDisposition,
this.contentEncoding == null ? "" : this.contentEncoding,
Expand All @@ -472,15 +490,16 @@ private String stringToSign(final UserDelegationKey key, String canonicalName) {

/**
* Gets the resource string for SAS token signing.
* @return
*
* @return The resource string.
*/
public String getResource() {
return this.resource;
}

/**
* Gets the permissions string for SAS token signing.
* @return
* @return The permissions string.
*/
public String getPermissions() {
return this.permissions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.azure.storage.common.sas.SasProtocol;

import java.time.OffsetDateTime;
import java.util.Map;

/**
* Used to initialize parameters for a Shared Access Signature (SAS) for an Azure Blob Storage service. Once all the
Expand Down Expand Up @@ -83,6 +84,8 @@ public final class BlobServiceSasSignatureValues {
private String correlationId;
private String encryptionScope;
private String delegatedUserObjectId;
private Map<String, String> requestHeaders;
private Map<String, String> requestQueryParameters;

/**
* Creates an object with empty values for all fields.
Expand Down Expand Up @@ -600,6 +603,58 @@ public BlobServiceSasSignatureValues setDelegatedUserObjectId(String delegatedUs
return this;
}

/**
* Optional. Beginning in version 2026-04-06, this value specifies Custom Request Headers to include in the SAS.
* Any usage of the SAS must include these headers and values in the request.
*
* <p>Note: This parameter is only valid for user delegation SAS. </p>
*
* @return The custom request headers to be set when the SAS is used.
*/
public Map<String, String> getRequestHeaders() {
return requestHeaders;
}

/**
* Optional. Beginning in version 2026-04-06, this value specifies Custom Request Headers to include in the SAS.
* Any usage of the SAS must include these headers and values in the request.
*
* <p>Note: This parameter is only valid for user delegation SAS. </p>
*
* @param requestHeaders The custom request headers to be set when the SAS is used.
* @return the updated BlobServiceSasSignatureValues object
*/
public BlobServiceSasSignatureValues setRequestHeaders(Map<String, String> requestHeaders) {
this.requestHeaders = requestHeaders;
return this;
}

/**
* Optional. Beginning in version 2026-04-06, this value specifies Custom Request Query Parameters to include in
* the SAS. Any usage of the SAS must include these query parameters and values in the request.
*
* <p>Note: This parameter is only valid for user delegation SAS. </p>
*
* @return The custom query parameters to be set when the SAS is used.
*/
public Map<String, String> getRequestQueryParameters() {
return requestQueryParameters;
}

/**
* Optional. Beginning in version 2026-04-06, this value specifies Custom Request Query Parameters to include in
* the SAS. Any usage of the SAS must include these query parameters and values in the request.
*
* <p>Note: This parameter is only valid for user delegation SAS. </p>
*
* @param requestQueryParameters The custom query parameters to be set when the SAS is used.
* @return the updated BlobServiceSasSignatureValues object
*/
public BlobServiceSasSignatureValues setRequestQueryParameters(Map<String, String> requestQueryParameters) {
this.requestQueryParameters = requestQueryParameters;
return this;
}

/**
* Uses an account's shared key credential to sign these signature values to produce the proper SAS query
* parameters.
Expand Down Expand Up @@ -713,8 +768,8 @@ public BlobServiceSasQueryParameters generateSasQueryParameters(UserDelegationKe
* 3. Reparse permissions depending on what the resource is. If it is an unrecognised resource, do nothing.
* <p>
* Taken from:
* https://github.com/Azure/azure-storage-blob-go/blob/master/azblob/sas_service.go#L33
* https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/storage/Azure.Storage.Blobs/src/Sas/BlobSasBuilder.cs
* <a href="https://github.com/Azure/azure-storage-blob-go/blob/master/azblob/sas_service.go#L33">sas_service.go</a>
* <a href="https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/storage/Azure.Storage.Blobs/src/Sas/BlobSasBuilder.cs">BlobSasBuilder.cs</a>
*/
private void ensureState() {
if (CoreUtils.isNullOrEmpty(blobName)) {
Expand Down
Loading
Loading