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
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ General:
Blob:

- Fixed blob operations hanging when a client disconnects before the operation queue processes the request. (issue #2575)
- Implement `PutBlobFromUrl` (`Put Blob From URL`), which previously returned 501. The source is fetched over loopback, as `PutBlockFromURL` already does, so that SAS authentication and the `x-ms-source-if-*` conditions are enforced by the existing download path. Standard blob properties are copied from the source unless `x-ms-copy-source-blob-properties` is false, request blob content headers override them either way, request metadata replaces the source's rather than adding to it, and `x-ms-copy-source-tag-option: COPY` reads the source's tags over that same authorized path. An `x-ms-source-content-md5`, `x-ms-blob-content-md5`, `Content-MD5`, or `x-ms-content-crc64` header is checked against the copied content, and the response reports the MD5 and CRC64 of that content. A SAS needs Create or Write to create the blob, Write to overwrite it, and Tag as well when the request sets tags with `x-ms-tags` or copies the source's. As with `CopyBlobFromURL`, only sources on the same Azurite instance are supported.

Table:

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,7 @@ Detailed support matrix:
- Abort Copy Blob (Only supports copy within same Azurite instance)
- Copy Blob From URL (Only supports copy within same Azurite instance, only on Loki)
- Put Block From URL (Only supports source within same Azurite instance)
- Put Blob From URL (Only supports source within same Azurite instance)
- Access control based on conditional headers
- Following features or REST APIs are NOT supported or limited supported in this release (will support more features per customers feedback in future releases)
- SharedKey Lite
Expand All @@ -1099,7 +1100,6 @@ Detailed support matrix:
- Concurrent Append
- Blob Expiry
- Object Replication Service
- Put Blob From URL
- Version Level Worm
- Sync copy blob by access source with oauth
- Encryption Scope
Expand Down
28 changes: 26 additions & 2 deletions src/blob/authentication/AccountSASAuthenticator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import IAccountDataStore from "../../common/IAccountDataStore";
import ILogger from "../../common/ILogger";
import StorageErrorFactory from "../errors/StorageErrorFactory";
import { BlobType } from "../generated/artifacts/models";
import { BlobCopySourceTags, BlobType } from "../generated/artifacts/models";
import Operation from "../generated/artifacts/operation";
import Context from "../generated/Context";
import IRequest from "../generated/IRequest";
Expand All @@ -14,7 +14,10 @@ import {
import IAuthenticator from "./IAuthenticator";
import OPERATION_ACCOUNT_SAS_PERMISSIONS from "./OperationAccountSASPermission";
import StrictModelNotSupportedError from "../errors/StrictModelNotSupportedError";
import { AUTHENTICATION_BEARERTOKEN_REQUIRED } from "../utils/constants";
import {
AUTHENTICATION_BEARERTOKEN_REQUIRED,
HeaderConstants
} from "../utils/constants";

export default class AccountSASAuthenticator implements IAuthenticator {
public constructor(
Expand Down Expand Up @@ -256,6 +259,7 @@ export default class AccountSASAuthenticator implements IAuthenticator {
// If copy destination blob exists, then permission must be Write only
if (
operation === Operation.BlockBlob_Upload ||
operation === Operation.BlockBlob_PutBlobFromUrl ||
operation === Operation.PageBlob_Create ||
operation === Operation.AppendBlob_Create ||
operation === Operation.Blob_StartCopyFromURL ||
Expand All @@ -280,6 +284,26 @@ export default class AccountSASAuthenticator implements IAuthenticator {
}
}

// Put Blob From URL sets tags on the destination when the request names
// them in x-ms-tags or asks for the source's to be copied, and Azure
// holds either to the Set Blob Tags permission on top of the write. A
// request that sets no tags takes only Create or Write.
if (
operation === Operation.BlockBlob_PutBlobFromUrl &&
(req.getHeader(HeaderConstants.X_MS_TAGS) !== undefined ||
req.getHeader(HeaderConstants.X_MS_COPY_SOURCE_TAG_OPTION) ===
BlobCopySourceTags.COPY) &&
!values.permissions.toString().includes(AccountSASPermission.Tag)
) {
this.logger.info(
`AccountSASAuthenticator:validate() For ${Operation[operation]}, setting tags on the destination requires the Tag permission.`,
context.contextId
);
throw StorageErrorFactory.getAuthorizationPermissionMismatch(
context.contextId!
);
}

this.logger.info(
`AccountSASAuthenticator:validate() Account SAS validation successfully.`,
context.contextId
Expand Down
32 changes: 30 additions & 2 deletions src/blob/authentication/BlobSASAuthenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ import ILogger from "../../common/ILogger";
import BlobStorageContext from "../context/BlobStorageContext";
import StorageErrorFactory from "../errors/StorageErrorFactory";
import StrictModelNotSupportedError from "../errors/StrictModelNotSupportedError";
import { AccessPolicy, BlobType } from "../generated/artifacts/models";
import {
AccessPolicy,
BlobCopySourceTags,
BlobType
} from "../generated/artifacts/models";
import Operation from "../generated/artifacts/operation";
import Context from "../generated/Context";
import IRequest from "../generated/IRequest";
import IBlobMetadataStore from "../persistence/IBlobMetadataStore";
import { AUTHENTICATION_BEARERTOKEN_REQUIRED } from "../utils/constants";
import {
AUTHENTICATION_BEARERTOKEN_REQUIRED,
HeaderConstants
} from "../utils/constants";
import { getUserDelegationKeyValue } from "../utils/utils";
import { BlobSASPermission } from "./BlobSASPermissions";
import { BlobSASResourceType } from "./BlobSASResourceType";
Expand Down Expand Up @@ -418,6 +425,7 @@ export default class BlobSASAuthenticator implements IAuthenticator {
// If copy destination blob exists, then permission must be Write only
if (
operation === Operation.BlockBlob_Upload ||
operation === Operation.BlockBlob_PutBlobFromUrl ||
operation === Operation.PageBlob_Create ||
operation === Operation.AppendBlob_Create ||
operation === Operation.Blob_StartCopyFromURL ||
Comment thread
jainakanksha-msft marked this conversation as resolved.
Expand All @@ -442,6 +450,26 @@ export default class BlobSASAuthenticator implements IAuthenticator {
}
}

// Put Blob From URL sets tags on the destination when the request names
// them in x-ms-tags or asks for the source's to be copied, and Azure
// holds either to the Set Blob Tags permission on top of the write. A
// request that sets no tags takes only Create or Write.
if (
operation === Operation.BlockBlob_PutBlobFromUrl &&
(req.getHeader(HeaderConstants.X_MS_TAGS) !== undefined ||
req.getHeader(HeaderConstants.X_MS_COPY_SOURCE_TAG_OPTION) ===
BlobCopySourceTags.COPY) &&
!values.permissions!.toString().includes(BlobSASPermission.Tag)
) {
this.logger.info(
`BlobSASAuthenticator:validate() For ${Operation[operation]}, setting tags on the destination requires the Tag permission.`,
context.contextId
);
throw StorageErrorFactory.getAuthorizationPermissionMismatch(
context.contextId!
);
}

this.logger.info(
`BlobSASAuthenticator:validate() Blob service SAS validation successfully.`,
context.contextId
Expand Down
11 changes: 11 additions & 0 deletions src/blob/authentication/OperationAccountSASPermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,17 @@ OPERATION_ACCOUNT_SAS_PERMISSIONS.set(
)
);

OPERATION_ACCOUNT_SAS_PERMISSIONS.set(
Operation.BlockBlob_PutBlobFromUrl,
new OperationAccountSASPermission(
AccountSASService.Blob,
AccountSASResourceType.Object,
// Create or Write creates the blob. Overwriting an existing one takes
// Write alone, which the authenticator checks separately.
AccountSASPermission.Write + AccountSASPermission.Create
)
);

OPERATION_ACCOUNT_SAS_PERMISSIONS.set(
Operation.PageBlob_Create,
new OperationAccountSASPermission(
Expand Down
16 changes: 16 additions & 0 deletions src/blob/authentication/OperationBlobSASPermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ OPERATION_BLOB_SAS_BLOB_PERMISSIONS.set(
BlobSASPermission.Write + BlobSASPermission.Create
)
);
OPERATION_BLOB_SAS_BLOB_PERMISSIONS.set(
Operation.BlockBlob_PutBlobFromUrl,
// Create or Write creates the blob. Overwriting an existing one takes
// Write alone, which the authenticator checks separately.
new OperationBlobSASPermission(
BlobSASPermission.Write + BlobSASPermission.Create
)
);
OPERATION_BLOB_SAS_BLOB_PERMISSIONS.set(
Operation.BlockBlob_StageBlock,
new OperationBlobSASPermission(BlobSASPermission.Write)
Expand Down Expand Up @@ -522,6 +530,14 @@ OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
BlobSASPermission.Write + BlobSASPermission.Create
)
);
OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
Operation.BlockBlob_PutBlobFromUrl,
// Create or Write creates the blob. Overwriting an existing one takes
// Write alone, which the authenticator checks separately.
new OperationBlobSASPermission(
BlobSASPermission.Write + BlobSASPermission.Create
)
);
OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
Operation.BlockBlob_StageBlock,
new OperationBlobSASPermission(BlobSASPermission.Write)
Expand Down
6 changes: 6 additions & 0 deletions src/blob/generated/artifacts/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4839,6 +4839,12 @@ export const BlockBlobPutBlobFromUrlHeaders: msRest.CompositeMapper = {
name: "ByteArray"
}
},
xMsContentCrc64: {
serializedName: "x-ms-content-crc64",
type: {
name: "ByteArray"
}
},
clientRequestId: {
serializedName: "x-ms-client-request-id",
type: {
Expand Down
10 changes: 10 additions & 0 deletions src/blob/generated/artifacts/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3077,6 +3077,10 @@ export interface BlockBlobPutBlobFromUrlOptionalParams {
* Specify the transactional md5 for the body, to be validated by the service.
*/
transactionalContentMD5?: Uint8Array;
/**
* Specify the transactional crc64 for the body, to be validated by the service.
*/
transactionalContentCrc64?: Uint8Array;
/**
* Optional. Specifies a user-defined name-value pair associated with the blob. If no name-value
* pairs are specified, the operation will copy the metadata from the source blob or file to the
Expand Down Expand Up @@ -5090,6 +5094,12 @@ export interface BlockBlobPutBlobFromUrlHeaders {
* is returned so that the client can check for message content integrity.
*/
contentMD5?: Uint8Array;
/**
* This header is returned so that the client can check for message content integrity. The value
* of this header is computed by the Blob service; it is not necessarily the same value specified
* in the request headers.
*/
xMsContentCrc64?: Uint8Array;
/**
* If a client request id header is sent in the request, this header will be present in the
* response with the same value.
Expand Down
1 change: 1 addition & 0 deletions src/blob/generated/artifacts/specifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2518,6 +2518,7 @@ const blockBlobPutBlobFromUrlOperationSpec: msRest.OperationSpec = {
],
headerParameters: [
Parameters.transactionalContentMD5,
Parameters.transactionalContentCrc64,
Parameters.contentLength,
Parameters.metadata,
Parameters.tier0,
Expand Down
Loading