Skip to content

Commit 1e7d69b

Browse files
authored
Filter by tags (#2311)
* Filter blob by tags * Added cases for filter/Tag permission check * Resove merge conflict
1 parent f2e1619 commit 1e7d69b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3353
-281
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,8 @@ Detailed support matrix:
981981
- OAuth authentication
982982
- Shared Access Signature Account Level
983983
- Shared Access Signature Service Level (Not support response header override in service SAS)
984-
- Container Public Access
984+
- Container Public Access
985+
- Blob Tags (preview)
985986
- Supported REST APIs
986987
- List Containers
987988
- Set Service Properties
@@ -1017,7 +1018,6 @@ Detailed support matrix:
10171018
- Soft delete & Undelete Container
10181019
- Soft delete & Undelete Blob
10191020
- Incremental Copy Blob
1020-
- Blob Tags
10211021
- Blob Query
10221022
- Blob Versions
10231023
- Blob Last Access Time

src/blob/authentication/ContainerSASPermissions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export enum ContainerSASPermission {
55
Write = "w",
66
Delete = "d",
77
List = "l",
8+
Filter = "f",
89
Any = "AnyPermission" // This is only for blob batch operation.
910
}

src/blob/authentication/OperationBlobSASPermission.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,10 @@ OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
369369
Operation.Container_ListBlobFlatSegment,
370370
new OperationBlobSASPermission(ContainerSASPermission.List)
371371
);
372+
OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
373+
Operation.Container_FilterBlobs,
374+
new OperationBlobSASPermission(ContainerSASPermission.Filter)
375+
);
372376
OPERATION_BLOB_SAS_CONTAINER_PERMISSIONS.set(
373377
Operation.Container_ListBlobHierarchySegment,
374378
new OperationBlobSASPermission(ContainerSASPermission.List)

src/blob/conditions/ConditionResourceAdapter.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { BlobModel, ContainerModel } from "../persistence/IBlobMetadataStore";
1+
import { BlobModel, ContainerModel, FilterBlobModel } from "../persistence/IBlobMetadataStore";
22
import IConditionResource from "./IConditionResource";
33

44
export default class ConditionResourceAdapter implements IConditionResource {
55
public exist: boolean;
66
public etag: string;
77
public lastModified: Date;
8+
public blobItemWithTags?: FilterBlobModel;
89

910
public constructor(resource: BlobModel | ContainerModel | undefined | null) {
1011
if (
@@ -33,5 +34,12 @@ export default class ConditionResourceAdapter implements IConditionResource {
3334

3435
this.lastModified = new Date(resource.properties.lastModified);
3536
this.lastModified.setMilliseconds(0); // Precision to seconds
37+
38+
const blobItem = resource as BlobModel;
39+
this.blobItemWithTags = {
40+
name: blobItem.name,
41+
containerName: blobItem.containerName,
42+
tags: blobItem.blobTags
43+
};
3644
}
3745
}

src/blob/conditions/ConditionalHeadersAdapter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export default class ConditionalHeadersAdapter implements IConditionalHeaders {
77
public ifUnmodifiedSince?: Date;
88
public ifMatch?: string[];
99
public ifNoneMatch?: string[];
10+
public ifTags?: string;
1011

1112
public constructor(
1213
context: Context,
@@ -43,5 +44,7 @@ export default class ConditionalHeadersAdapter implements IConditionalHeaders {
4344
if (this.ifUnmodifiedSince) {
4445
this.ifUnmodifiedSince.setMilliseconds(0); // Precision to seconds
4546
}
47+
48+
this.ifTags = modifiedAccessConditions.ifTags;
4649
}
4750
}

src/blob/conditions/IConditionResource.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { FilterBlobModel } from "../persistence/IBlobMetadataStore";
2+
13
export default interface IConditionResource {
24
/**
35
* Whether resource exists or not.
@@ -13,4 +15,5 @@ export default interface IConditionResource {
1315
* last modified time for container or blob.
1416
*/
1517
lastModified: Date;
18+
blobItemWithTags?: FilterBlobModel;
1619
}

src/blob/conditions/IConditionalHeaders.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,9 @@ export interface IConditionalHeaders {
1111
* If-None-Match etag list without quotes.
1212
*/
1313
ifNoneMatch?: string[];
14+
15+
/**
16+
* Specify a SQL where clause on blob tags to operate only on blobs with a matching value.
17+
*/
18+
ifTags?: string;
1419
}

src/blob/conditions/IConditionalHeadersValidator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface IConditionalHeadersValidator {
66
validate(
77
context: Context,
88
conditionalHeaders: IConditionalHeaders,
9-
resource: IConditionResource
9+
resource: IConditionResource,
10+
isSourceBlob?: boolean
1011
): void;
1112
}

src/blob/conditions/ReadConditionalHeadersValidator.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import StorageErrorFactory from "../errors/StorageErrorFactory";
22
import { ModifiedAccessConditions } from "../generated/artifacts/models";
33
import Context from "../generated/Context";
44
import { BlobModel, ContainerModel } from "../persistence/IBlobMetadataStore";
5+
import { generateQueryBlobWithTagsWhereFunction } from "../persistence/QueryInterpreter/QueryInterpreter";
56
import ConditionalHeadersAdapter from "./ConditionalHeadersAdapter";
67
import ConditionResourceAdapter from "./ConditionResourceAdapter";
78
import { IConditionalHeaders } from "./IConditionalHeaders";
@@ -11,12 +12,14 @@ import IConditionResource from "./IConditionResource";
1112
export function validateReadConditions(
1213
context: Context,
1314
conditionalHeaders?: ModifiedAccessConditions,
14-
model?: BlobModel | ContainerModel | null
15+
model?: BlobModel | ContainerModel | null,
16+
isSourceBlob?: boolean
1517
) {
1618
new ReadConditionalHeadersValidator().validate(
1719
context,
1820
new ConditionalHeadersAdapter(context, conditionalHeaders),
19-
new ConditionResourceAdapter(model)
21+
new ConditionResourceAdapter(model),
22+
isSourceBlob
2023
);
2124
}
2225

@@ -30,11 +33,13 @@ export default class ReadConditionalHeadersValidator
3033
* @param context
3134
* @param conditionalHeaders
3235
* @param resource
36+
* @param isSourceBlob
3337
*/
3438
public validate(
3539
context: Context,
3640
conditionalHeaders: IConditionalHeaders,
37-
resource: IConditionResource
41+
resource: IConditionResource,
42+
isSourceBlob?: boolean
3843
): void {
3944
// If-Match && If-Unmodified-Since && (If-None-Match || If-Modified-Since)
4045

@@ -66,7 +71,7 @@ export default class ReadConditionalHeadersValidator
6671
// If-Match
6772
const ifMatchPass = conditionalHeaders.ifMatch
6873
? conditionalHeaders.ifMatch.includes(resource.etag) ||
69-
conditionalHeaders.ifMatch[0] === "*"
74+
conditionalHeaders.ifMatch[0] === "*"
7075
: undefined;
7176

7277
// If-Unmodified-Since
@@ -107,6 +112,16 @@ export default class ReadConditionalHeadersValidator
107112
if (isModifiedSincePass === false && ifNoneMatchPass !== true) {
108113
throw StorageErrorFactory.getNotModified(context.contextId!);
109114
}
115+
116+
if (conditionalHeaders.ifTags) {
117+
const againstSourceBlob = isSourceBlob === undefined ? false : isSourceBlob;
118+
const validateFunction = generateQueryBlobWithTagsWhereFunction(context, conditionalHeaders.ifTags, againstSourceBlob ? 'x-ms-source-if-tags' : 'x-ms-if-tags');
119+
120+
if (conditionalHeaders?.ifTags !== undefined
121+
&& validateFunction(resource.blobItemWithTags).length === 0) {
122+
throw StorageErrorFactory.getConditionNotMet(context.contextId!);
123+
}
124+
}
110125
}
111126
}
112127
}

src/blob/conditions/WriteConditionalHeadersValidator.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from "../generated/artifacts/models";
66
import Context from "../generated/Context";
77
import { BlobModel, ContainerModel } from "../persistence/IBlobMetadataStore";
8+
import { generateQueryBlobWithTagsWhereFunction } from "../persistence/QueryInterpreter/QueryInterpreter";
89
import ConditionalHeadersAdapter from "./ConditionalHeadersAdapter";
910
import ConditionResourceAdapter from "./ConditionResourceAdapter";
1011
import { IConditionalHeaders } from "./IConditionalHeaders";
@@ -29,7 +30,7 @@ export function validateSequenceNumberWriteConditions(
2930
if (
3031
conditionalHeaders.ifSequenceNumberLessThanOrEqualTo !== undefined &&
3132
conditionalHeaders.ifSequenceNumberLessThanOrEqualTo <
32-
model.properties.blobSequenceNumber
33+
model.properties.blobSequenceNumber
3334
) {
3435
throw StorageErrorFactory.getSequenceNumberConditionNotMet(
3536
context.contextId!
@@ -39,7 +40,7 @@ export function validateSequenceNumberWriteConditions(
3940
if (
4041
conditionalHeaders.ifSequenceNumberLessThan !== undefined &&
4142
conditionalHeaders.ifSequenceNumberLessThan <=
42-
model.properties.blobSequenceNumber
43+
model.properties.blobSequenceNumber
4344
) {
4445
throw StorageErrorFactory.getSequenceNumberConditionNotMet(
4546
context.contextId!
@@ -49,7 +50,7 @@ export function validateSequenceNumberWriteConditions(
4950
if (
5051
conditionalHeaders.ifSequenceNumberEqualTo !== undefined &&
5152
conditionalHeaders.ifSequenceNumberEqualTo !==
52-
model.properties.blobSequenceNumber
53+
model.properties.blobSequenceNumber
5354
) {
5455
throw StorageErrorFactory.getSequenceNumberConditionNotMet(
5556
context.contextId!
@@ -167,6 +168,15 @@ export default class WriteConditionalHeadersValidator
167168
}
168169
return;
169170
}
171+
172+
if (conditionalHeaders.ifTags) {
173+
const validateFunction = generateQueryBlobWithTagsWhereFunction(context, conditionalHeaders.ifTags, 'x-ms-if-tags');
174+
175+
if (conditionalHeaders?.ifTags !== undefined
176+
&& validateFunction(resource.blobItemWithTags).length === 0) {
177+
throw StorageErrorFactory.getConditionNotMet(context.contextId!);
178+
}
179+
}
170180
}
171181
}
172182

0 commit comments

Comments
 (0)