Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

## Upcoming Release

Blob:

- Added support for delegation SAS version 2026-04-06.

## 2026.06 Version 3.36.0

General:
Expand Down
12 changes: 11 additions & 1 deletion src/blob/authentication/BlobSASAuthenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -520,8 +527,11 @@ export default class BlobSASAuthenticator implements IAuthenticator {
signedTenantId,
signedService,
signedVersion,
delegatedUserTenantId,
signedStartsOn,
signedExpiresOn
signedExpiresOn,
signedRequestHeaders,
signedRequestQueryParameters
};

return blobSASValues;
Expand Down
97 changes: 97 additions & 0 deletions src/blob/authentication/IBlobSASSignatureValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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."
);
}
Comment thread
Socolin marked this conversation as resolved.

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");
Comment thread
Socolin marked this conversation as resolved.

const signature = computeHMACSHA256(stringToSign, userDelegationKeyValue);
return [signature, stringToSign];
}

function getCanonicalName(
accountName: string,
containerName: string,
Expand Down