diff --git a/ChangeLog.md b/ChangeLog.md index 96358563f..74b6e867d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,10 @@ ## Upcoming Release +Blob: + +- Added support for delegation SAS version 2026-04-06. + ## 2026.06 Version 3.36.0 General: diff --git a/src/blob/authentication/BlobSASAuthenticator.ts b/src/blob/authentication/BlobSASAuthenticator.ts index ac848b0f8..ecc6879a4 100644 --- a/src/blob/authentication/BlobSASAuthenticator.ts +++ b/src/blob/authentication/BlobSASAuthenticator.ts @@ -478,7 +478,14 @@ export default class BlobSASAuthenticator implements IAuthenticator { const signedStartsOn = this.decodeIfExist(req.getQuery("skt")); const signedExpiresOn = this.decodeIfExist(req.getQuery("ske")); const signedVersion = this.decodeIfExist(req.getQuery("skv")); + const delegatedUserTenantId = this.decodeIfExist(req.getQuery("skdutid")); const signedService = this.decodeIfExist(req.getQuery("sks")); + const signedRequestHeaders = this.decodeIfExist( + req.getQuery("srh") + ); + const signedRequestQueryParameters = this.decodeIfExist( + req.getQuery("srq") + ); if (!identifier && (!permissions || !expiryTime)) { this.logger.warn( @@ -520,8 +527,11 @@ export default class BlobSASAuthenticator implements IAuthenticator { signedTenantId, signedService, signedVersion, + delegatedUserTenantId, signedStartsOn, - signedExpiresOn + signedExpiresOn, + signedRequestHeaders, + signedRequestQueryParameters }; return blobSASValues; diff --git a/src/blob/authentication/IBlobSASSignatureValues.ts b/src/blob/authentication/IBlobSASSignatureValues.ts index 12cf452be..2a4a17966 100644 --- a/src/blob/authentication/IBlobSASSignatureValues.ts +++ b/src/blob/authentication/IBlobSASSignatureValues.ts @@ -106,6 +106,22 @@ export interface IBlobSASSignatureValues { */ cacheControl?: string; + /** + * Optional. Custom Request Headers to include in the SAS + * + * @type {string} + * @memberof IBlobSASSignatureValues + */ + signedRequestHeaders?: string; + + /** + * Optional. Custom Request Query Parameters to include in the SAS + * + * @type {string} + * @memberof IBlobSASSignatureValues + */ + signedRequestQueryParameters?: string; + /** * Optional. The content-disposition header for the SAS. * @@ -292,6 +308,14 @@ export function generateBlobSASSignatureWithUDK( accountName: string, udkValue: Buffer ): [string, string] { + if (blobSASSignatureValues.version >= "2026-04-06") { + return generateBlobSASBlobSASSignatureWithUDK20260406( + blobSASSignatureValues, + resource, + accountName, + udkValue + ); + } if (blobSASSignatureValues.version >= "2025-07-05") { return generateBlobSASBlobSASSignatureWithUDK20250705( blobSASSignatureValues, @@ -803,6 +827,79 @@ function generateBlobSASBlobSASSignatureWithUDK20250705( return [signature, stringToSign]; } +function generateBlobSASBlobSASSignatureWithUDK20260406( + blobSASSignatureValues: IBlobSASSignatureValues, + resource: BlobSASResourceType, + accountName: string, + userDelegationKeyValue: Buffer +): [string, string] { + if ( + !blobSASSignatureValues.identifier && + !blobSASSignatureValues.permissions && + !blobSASSignatureValues.expiryTime + ) { + throw new RangeError( + // tslint:disable-next-line:max-line-length + "generateBlobSASSignature(): Must provide 'permissions' and 'expiryTime' for Blob SAS generation when 'identifier' is not provided." + ); + } + + const verifiedPermissions = blobSASSignatureValues.permissions; + + // Signature is generated on the un-url-encoded values. + const stringToSign = [ + verifiedPermissions ? verifiedPermissions : "", + blobSASSignatureValues.startTime === undefined + ? "" + : typeof blobSASSignatureValues.startTime === "string" + ? blobSASSignatureValues.startTime + : truncatedISO8061Date(blobSASSignatureValues.startTime, false), + blobSASSignatureValues.expiryTime === undefined + ? "" + : typeof blobSASSignatureValues.expiryTime === "string" + ? blobSASSignatureValues.expiryTime + : truncatedISO8061Date(blobSASSignatureValues.expiryTime, false), + getCanonicalName( + accountName, + blobSASSignatureValues.containerName, + resource === BlobSASResourceType.Blob + ? blobSASSignatureValues.blobName + : "" + ), + blobSASSignatureValues.signedObjectId, + blobSASSignatureValues.signedTenantId, + blobSASSignatureValues.signedStartsOn, + blobSASSignatureValues.signedExpiresOn, + blobSASSignatureValues.signedService, + blobSASSignatureValues.signedVersion, + undefined, // blobSASSignatureValues.preauthorizedAgentObjectId, + undefined, // agentObjectId + undefined, // blobSASSignatureValues.correlationId, + blobSASSignatureValues.delegatedUserTenantId, // SignedKeyDelegatedUserTenantId, will be added in a future release. + blobSASSignatureValues.delegatedUserObjectId, // SignedDelegatedUserObjectId, will be added in future release. + blobSASSignatureValues.ipRange === undefined + ? "" + : typeof blobSASSignatureValues.ipRange === "string" + ? blobSASSignatureValues.ipRange + : ipRangeToString(blobSASSignatureValues.ipRange), + blobSASSignatureValues.protocol ? blobSASSignatureValues.protocol : "", + blobSASSignatureValues.version, + resource, + undefined, // blob version timestamp, + blobSASSignatureValues.encryptionScope, + blobSASSignatureValues.signedRequestHeaders, + blobSASSignatureValues.signedRequestQueryParameters, + blobSASSignatureValues.cacheControl, + blobSASSignatureValues.contentDisposition, + blobSASSignatureValues.contentEncoding, + blobSASSignatureValues.contentLanguage, + blobSASSignatureValues.contentType + ].join("\n"); + + const signature = computeHMACSHA256(stringToSign, userDelegationKeyValue); + return [signature, stringToSign]; +} + function getCanonicalName( accountName: string, containerName: string,