diff --git a/x-pack/plugins/cases/common/api/cases/case.ts b/x-pack/plugins/cases/common/api/cases/case.ts index 8f1e7a1264166..acfca22e70498 100644 --- a/x-pack/plugins/cases/common/api/cases/case.ts +++ b/x-pack/plugins/cases/common/api/cases/case.ts @@ -13,6 +13,12 @@ import { CommentRt } from './comment'; import { CasesStatusResponseRt, CaseStatusRt } from './status'; import { CaseConnectorRt } from '../connectors/connector'; import { CaseAssigneesRt } from './assignee'; +import { + MAX_ASSIGNEES_FILTER_LENGTH, + MAX_REPORTERS_FILTER_LENGTH, + MAX_TAGS_FILTER_LENGTH, +} from '../../constants'; +import { limitedArraySchema } from '../../schema'; export const AttachmentTotalsRt = rt.strict({ alerts: rt.number, @@ -207,7 +213,7 @@ export const CasesFindRequestRt = rt.exact( /** * Tags to filter by */ - tags: rt.union([rt.array(rt.string), rt.string]), + tags: rt.union([limitedArraySchema(rt.string, 0, MAX_TAGS_FILTER_LENGTH, 'tags'), rt.string]), /** * The status of the case (open, closed, in-progress) */ @@ -219,11 +225,17 @@ export const CasesFindRequestRt = rt.exact( /** * The uids of the user profiles to filter by */ - assignees: rt.union([rt.array(rt.string), rt.string]), + assignees: rt.union([ + limitedArraySchema(rt.string, 0, MAX_ASSIGNEES_FILTER_LENGTH, 'assignees'), + rt.string, + ]), /** * The reporters to filter by */ - reporters: rt.union([rt.array(rt.string), rt.string]), + reporters: rt.union([ + limitedArraySchema(rt.string, 0, MAX_REPORTERS_FILTER_LENGTH, 'reporters'), + rt.string, + ]), /** * Operator to use for the `search` field */ diff --git a/x-pack/plugins/cases/common/constants/index.ts b/x-pack/plugins/cases/common/constants/index.ts index 1a49de004e73f..24365cdc625e8 100644 --- a/x-pack/plugins/cases/common/constants/index.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -106,6 +106,9 @@ export const MAX_CONCURRENT_SEARCHES = 10 as const; export const MAX_BULK_GET_CASES = 1000 as const; export const MAX_COMMENTS_PER_PAGE = 100 as const; export const MAX_CATEGORY_FILTER_LENGTH = 100 as const; +export const MAX_TAGS_FILTER_LENGTH = 100 as const; +export const MAX_ASSIGNEES_FILTER_LENGTH = 100 as const; +export const MAX_REPORTERS_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 c025e43dc9044..2ce8031bc1cb9 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -3826,7 +3826,8 @@ "type": "array", "items": { "type": "string" - } + }, + "maxItems": 100 } ] } @@ -3922,7 +3923,8 @@ "type": "array", "items": { "type": "string" - } + }, + "maxItems": 100 } ] }, @@ -4023,7 +4025,8 @@ "type": "array", "items": { "type": "string" - } + }, + "maxItems": 100 } ] }, diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index ecbe10114a053..50184a7b96742 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -2323,6 +2323,7 @@ components: - type: array items: type: string + maxItems: 100 category: in: query name: category @@ -2389,6 +2390,7 @@ components: - type: array items: type: string + maxItems: 100 example: elastic search: in: query @@ -2460,6 +2462,7 @@ components: - type: array items: type: string + maxItems: 100 example: tag-1 to: in: query diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/assignees.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/assignees.yaml index 4ce794cbc879c..a4c81c67f6c67 100644 --- a/x-pack/plugins/cases/docs/openapi/components/parameters/assignees.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/parameters/assignees.yaml @@ -9,4 +9,5 @@ schema: - type: string - type: array items: - type: string \ No newline at end of file + type: string + maxItems: 100 diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/reporters.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/reporters.yaml index 6fefa32f011dd..db28a6c48ae02 100644 --- a/x-pack/plugins/cases/docs/openapi/components/parameters/reporters.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/parameters/reporters.yaml @@ -7,4 +7,5 @@ schema: - type: array items: type: string -example: elastic \ No newline at end of file + maxItems: 100 +example: elastic diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/tags.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/tags.yaml index bff378ac1fbda..d899edbcc38eb 100644 --- a/x-pack/plugins/cases/docs/openapi/components/parameters/tags.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/parameters/tags.yaml @@ -7,4 +7,5 @@ schema: - type: array items: type: string -example: tag-1 \ No newline at end of file + maxItems: 100 +example: tag-1 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 968f111b58516..b7d9d9117ad7e 100644 --- a/x-pack/plugins/cases/server/client/cases/find.test.ts +++ b/x-pack/plugins/cases/server/client/cases/find.test.ts @@ -8,7 +8,12 @@ import { v1 as uuidv1 } from 'uuid'; import type { Case } from '../../../common/api'; -import { MAX_CATEGORY_FILTER_LENGTH } from '../../../common/constants'; +import { + MAX_ASSIGNEES_FILTER_LENGTH, + MAX_CATEGORY_FILTER_LENGTH, + MAX_REPORTERS_FILTER_LENGTH, + MAX_TAGS_FILTER_LENGTH, +} from '../../../common/constants'; import { flattenCaseSavedObject } from '../../common/utils'; import { mockCases } from '../../mocks'; import { createCasesClientMockArgs, createCasesClientMockFindRequest } from '../mocks'; @@ -114,5 +119,35 @@ describe('find', () => { `Error: Too many categories provided. The maximum allowed is ${MAX_CATEGORY_FILTER_LENGTH}` ); }); + + it(`throws an error when the tags array has ${MAX_TAGS_FILTER_LENGTH} items`, async () => { + const tags = Array(MAX_TAGS_FILTER_LENGTH + 1).fill('foobar'); + + const findRequest = createCasesClientMockFindRequest({ tags }); + + await expect(find(findRequest, clientArgs)).rejects.toThrowError( + `Error: The length of the field tags is too long. Array must be of length <= ${MAX_TAGS_FILTER_LENGTH}` + ); + }); + + it(`throws an error when the assignees array has ${MAX_ASSIGNEES_FILTER_LENGTH} items`, async () => { + const assignees = Array(MAX_ASSIGNEES_FILTER_LENGTH + 1).fill('foobar'); + + const findRequest = createCasesClientMockFindRequest({ assignees }); + + await expect(find(findRequest, clientArgs)).rejects.toThrowError( + `Error: The length of the field assignees is too long. Array must be of length <= ${MAX_ASSIGNEES_FILTER_LENGTH}` + ); + }); + + it(`throws an error when the reporters array has ${MAX_REPORTERS_FILTER_LENGTH} items`, async () => { + const reporters = Array(MAX_REPORTERS_FILTER_LENGTH + 1).fill('foobar'); + + const findRequest = createCasesClientMockFindRequest({ reporters }); + + await expect(find(findRequest, clientArgs)).rejects.toThrowError( + `Error: The length of the field reporters is too long. Array must be of length <= ${MAX_REPORTERS_FILTER_LENGTH}.` + ); + }); }); }); 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 00bdaa5b25f0b..6875bc043a6c5 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,13 @@ import { v1 as uuidv1 } from 'uuid'; import expect from '@kbn/expect'; -import { CASES_URL, MAX_CATEGORY_FILTER_LENGTH } from '@kbn/cases-plugin/common/constants'; +import { + CASES_URL, + MAX_ASSIGNEES_FILTER_LENGTH, + MAX_CATEGORY_FILTER_LENGTH, + MAX_REPORTERS_FILTER_LENGTH, + MAX_TAGS_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'; @@ -307,20 +313,6 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('unhappy path - 400s when bad query supplied', async () => { - await findCases({ supertest, query: { perPage: true }, expectedHttpCode: 400 }); - }); - - for (const field of ['owner', 'tags', 'severity', 'status']) { - it(`should return a 400 when attempting to query a keyword field [${field}] when using a wildcard query`, async () => { - await findCases({ - supertest, - query: { searchFields: [field], search: 'some search string*' }, - expectedHttpCode: 400, - }); - }); - } - it('sorts by severity', async () => { const case4 = await createCase(supertest, { ...postCaseReq, @@ -349,10 +341,37 @@ 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'); + describe('errors', () => { + it('unhappy path - 400s when bad query supplied', async () => { + await findCases({ supertest, query: { perPage: true }, expectedHttpCode: 400 }); + }); + + for (const field of ['owner', 'tags', 'severity', 'status']) { + it(`should return a 400 when attempting to query a keyword field [${field}] when using a wildcard query`, async () => { + await findCases({ + supertest, + query: { searchFields: [field], search: 'some search string*' }, + expectedHttpCode: 400, + }); + }); + } - await findCases({ supertest, query: { category }, expectedHttpCode: 400 }); + for (const scenario of [ + { fieldName: 'category', sizeLimit: MAX_CATEGORY_FILTER_LENGTH }, + { fieldName: 'tags', sizeLimit: MAX_TAGS_FILTER_LENGTH }, + { fieldName: 'assignees', sizeLimit: MAX_ASSIGNEES_FILTER_LENGTH }, + { fieldName: 'reporters', sizeLimit: MAX_REPORTERS_FILTER_LENGTH }, + ]) { + it(`unhappy path - 400s when the field ${scenario.fieldName} exceeds the size limit`, async () => { + const value = Array(scenario.sizeLimit + 1).fill('foobar'); + + await findCases({ + supertest, + query: { [scenario.fieldName]: value }, + expectedHttpCode: 400, + }); + }); + } }); describe('search and searchField', () => {