diff --git a/docs/api-generated/cases/case-apis-passthru.asciidoc b/docs/api-generated/cases/case-apis-passthru.asciidoc
index d983ab1d2a09..d0e7a67c5066 100644
--- a/docs/api-generated/cases/case-apis-passthru.asciidoc
+++ b/docs/api-generated/cases/case-apis-passthru.asciidoc
@@ -724,7 +724,9 @@ Any modifications made to this file will be overwritten.
assignees (optional)
-
Query Parameter — Filters the returned cases by assignees. Valid values are none
or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API. default: null
defaultSearchOperator (optional)
+
Query Parameter — Filters the returned cases by assignees. Valid values are none
or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API. default: null
category (optional)
+
+
Query Parameter — Filters the returned cases by category. Limited to 100 categories. default: null
defaultSearchOperator (optional)
Query Parameter — The default operator to use for the simple_query_string. default: OR
from (optional)
diff --git a/x-pack/plugins/cases/common/constants/index.ts b/x-pack/plugins/cases/common/constants/index.ts
index 0407315328df..33473a9c80bf 100644
--- a/x-pack/plugins/cases/common/constants/index.ts
+++ b/x-pack/plugins/cases/common/constants/index.ts
@@ -104,6 +104,7 @@ export const MAX_DOCS_PER_PAGE = 10000 as const;
export const MAX_BULK_GET_ATTACHMENTS = MAX_DOCS_PER_PAGE;
export const MAX_CONCURRENT_SEARCHES = 10 as const;
export const MAX_BULK_GET_CASES = 1000 as const;
+export const MAX_CATEGORY_FILTER_LENGTH = 100 as const;
/**
* Validation
diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json
index aee1e1aa751a..92dcfe577930 100644
--- a/x-pack/plugins/cases/docs/openapi/bundled.json
+++ b/x-pack/plugins/cases/docs/openapi/bundled.json
@@ -236,6 +236,25 @@
]
}
},
+ {
+ "name": "category",
+ "in": "query",
+ "description": "Filters the returned cases by category. Limited to 100 categories.",
+ "schema": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ "example": "my-category"
+ },
{
"name": "defaultSearchOperator",
"in": "query",
diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml
index 8b93529b76b7..3d53916c89df 100644
--- a/x-pack/plugins/cases/docs/openapi/bundled.yaml
+++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml
@@ -140,6 +140,16 @@ paths:
- type: array
items:
type: string
+ - name: category
+ in: query
+ description: Filters the returned cases by category. Limited to 100 categories.
+ schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ example: my-category
- name: defaultSearchOperator
in: query
description: The default operator to use for the simple_query_string.
diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml
index 4ddd52db231f..e04fbfe020c9 100644
--- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml
+++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml
@@ -18,6 +18,16 @@ get:
- type: array
items:
type: string
+ - name: category
+ in: query
+ description: Filters the returned cases by category. Limited to 100 categories.
+ schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ example: my-category
- name: defaultSearchOperator
in: query
description: The default operator to use for the simple_query_string.
diff --git a/x-pack/plugins/cases/server/client/cases/find.test.ts b/x-pack/plugins/cases/server/client/cases/find.test.ts
index 1e5afd2c87b3..968f111b5851 100644
--- a/x-pack/plugins/cases/server/client/cases/find.test.ts
+++ b/x-pack/plugins/cases/server/client/cases/find.test.ts
@@ -8,6 +8,7 @@ import { v1 as uuidv1 } from 'uuid';
import type { Case } from '../../../common/api';
+import { MAX_CATEGORY_FILTER_LENGTH } from '../../../common/constants';
import { flattenCaseSavedObject } from '../../common/utils';
import { mockCases } from '../../mocks';
import { createCasesClientMockArgs, createCasesClientMockFindRequest } from '../mocks';
@@ -103,5 +104,15 @@ describe('find', () => {
'Error: Invalid value "foobar" supplied to "searchFields"'
);
});
+
+ it(`throws an error when the category array has ${MAX_CATEGORY_FILTER_LENGTH} items`, async () => {
+ const category = Array(MAX_CATEGORY_FILTER_LENGTH + 1).fill('foobar');
+
+ const findRequest = createCasesClientMockFindRequest({ category });
+
+ await expect(find(findRequest, clientArgs)).rejects.toThrow(
+ `Error: Too many categories provided. The maximum allowed is ${MAX_CATEGORY_FILTER_LENGTH}`
+ );
+ });
});
});
diff --git a/x-pack/plugins/cases/server/client/cases/find.ts b/x-pack/plugins/cases/server/client/cases/find.ts
index 8e9e68f79d66..aa2bb3676820 100644
--- a/x-pack/plugins/cases/server/client/cases/find.ts
+++ b/x-pack/plugins/cases/server/client/cases/find.ts
@@ -8,6 +8,7 @@
import { isEmpty } from 'lodash';
import Boom from '@hapi/boom';
+import { MAX_CATEGORY_FILTER_LENGTH } from '../../../common/constants';
import type { CasesFindResponse, CasesFindRequest } from '../../../common/api';
import {
CasesFindRequestRt,
@@ -24,6 +25,16 @@ import { LICENSING_CASE_ASSIGNMENT_FEATURE } from '../../common/constants';
import type { CasesFindQueryParams } from '../types';
import { decodeOrThrow } from '../../../common/api/runtime_types';
+/**
+ * Throws an error if the user tries to filter by more than MAX_CATEGORY_FILTER_LENGTH categories.
+ */
+function throwIfCategoryParamTooLong(category?: string[] | string) {
+ if (Array.isArray(category) && category.length > MAX_CATEGORY_FILTER_LENGTH)
+ throw Boom.badRequest(
+ `Too many categories provided. The maximum allowed is ${MAX_CATEGORY_FILTER_LENGTH}`
+ );
+}
+
/**
* Retrieves a case and optionally its comments.
*
@@ -44,8 +55,7 @@ export const find = async (
try {
const queryParams = decodeWithExcessOrThrow(CasesFindRequestRt)(params);
- const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } =
- await authorization.getAuthorizationFilter(Operations.findCases);
+ throwIfCategoryParamTooLong(queryParams.category);
/**
* Assign users to a case is only available to Platinum+
@@ -63,6 +73,9 @@ export const find = async (
licensingService.notifyUsage(LICENSING_CASE_ASSIGNMENT_FEATURE);
}
+ const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } =
+ await authorization.getAuthorizationFilter(Operations.findCases);
+
const queryArgs: CasesFindQueryParams = {
tags: queryParams.tags,
reporters: queryParams.reporters,
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts
index 60b1e789ec77..00bdaa5b25f0 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts
@@ -8,7 +8,7 @@
import { v1 as uuidv1 } from 'uuid';
import expect from '@kbn/expect';
-import { CASES_URL } from '@kbn/cases-plugin/common/constants';
+import { CASES_URL, MAX_CATEGORY_FILTER_LENGTH } from '@kbn/cases-plugin/common/constants';
import { Case, CaseSeverity, CaseStatuses, CommentType } from '@kbn/cases-plugin/common/api';
import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
@@ -349,6 +349,12 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
+ it('unhappy path - 400s when more than the maximum category fields are supplied', async () => {
+ const category = Array(MAX_CATEGORY_FILTER_LENGTH + 1).fill('foobar');
+
+ await findCases({ supertest, query: { category }, expectedHttpCode: 400 });
+ });
+
describe('search and searchField', () => {
beforeEach(async () => {
await createCase(supertest, postCaseReq);