Skip to content

Commit

Permalink
[Cases] Add guardrails for add and update comment API (#161200)
Browse files Browse the repository at this point in the history
Connected to #146945

## Summary

| Description  | Limit | Done? | Documented?
| ------------- | ---- | :---: | ---- |
| Total number of comment characters | 30.000 | ✅ | Yes
|

- Tests.
- Updated Documentation.

### Checklist

Delete any items that are not applicable to this PR.

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

### Release Notes

Post and Patch comment API limits total number of characters per comment
to 30000

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
js-jankisalvi and kibanamachine authored Jul 6, 2023
1 parent 75140a8 commit 8543d5f
Show file tree
Hide file tree
Showing 14 changed files with 451 additions and 4 deletions.
66 changes: 66 additions & 0 deletions x-pack/plugins/cases/common/api/cases/comment/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import { PathReporter } from 'io-ts/lib/PathReporter';

import {
CommentAttributesBasicRt,
CommentType,
Expand All @@ -29,6 +31,7 @@ import {
BulkGetAttachmentsRequestRt,
BulkGetAttachmentsResponseRt,
} from '.';
import { MAX_COMMENT_LENGTH } from '../../../constants';

describe('Comments', () => {
describe('CommentAttributesBasicRt', () => {
Expand Down Expand Up @@ -323,6 +326,7 @@ describe('Comments', () => {
type: CommentType.user,
owner: 'cases',
};

it('has expected attributes in request', () => {
const query = CommentRequestRt.decode(defaultRequest);

Expand All @@ -340,6 +344,68 @@ describe('Comments', () => {
right: defaultRequest,
});
});

describe('errors', () => {
describe('commentType: user', () => {
it('throws error when comment is too long', () => {
const longComment = 'x'.repeat(MAX_COMMENT_LENGTH + 1);

expect(
PathReporter.report(
CommentRequestRt.decode({ ...defaultRequest, comment: longComment })
)
).toContain('The length of the comment is too long. The maximum length is 30000.');
});

it('throws error when comment is empty', () => {
expect(
PathReporter.report(CommentRequestRt.decode({ ...defaultRequest, comment: '' }))
).toContain('The comment field cannot be an empty string.');
});

it('throws error when comment string of empty characters', () => {
expect(
PathReporter.report(CommentRequestRt.decode({ ...defaultRequest, comment: ' ' }))
).toContain('The comment field cannot be an empty string.');
});
});

describe('commentType: action', () => {
const request = {
type: CommentType.actions,
actions: {
targets: [
{
hostname: 'host1',
endpointId: '001',
},
],
type: 'isolate',
},
owner: 'cases',
};

it('throws error when comment is too long', () => {
const longComment = 'x'.repeat(MAX_COMMENT_LENGTH + 1);

expect(
PathReporter.report(CommentRequestRt.decode({ ...request, comment: longComment }))
).toContain('The length of the comment is too long. The maximum length is 30000.');
});

it('throws error when comment is empty', () => {
expect(
PathReporter.report(CommentRequestRt.decode({ ...request, comment: '' }))
).toContain('The comment field cannot be an empty string.');
});

it('throws error when comment string of empty characters', () => {
expect(
PathReporter.report(CommentRequestRt.decode({ ...request, comment: ' ' }))
).toContain('The comment field cannot be an empty string.');
});
});
});
});

describe('CommentRt', () => {
Expand Down
34 changes: 31 additions & 3 deletions x-pack/plugins/cases/common/api/cases/comment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
*/

import * as rt from 'io-ts';
import { MAX_BULK_GET_ATTACHMENTS, MAX_COMMENTS_PER_PAGE } from '../../../constants';
import { limitedArraySchema, paginationSchema } from '../../../schema';
import {
MAX_BULK_GET_ATTACHMENTS,
MAX_COMMENTS_PER_PAGE,
MAX_COMMENT_LENGTH,
} from '../../../constants';
import { limitedArraySchema, paginationSchema, limitedStringSchema } from '../../../schema';
import { jsonValueRt } from '../../runtime_types';

import { UserRt } from '../../user';
Expand Down Expand Up @@ -193,7 +197,31 @@ const BasicCommentRequestRt = rt.union([
PersistableStateAttachmentRt,
]);

export const CommentRequestRt = rt.union([BasicCommentRequestRt, ExternalReferenceSORt]);
export const CommentRequestRt = rt.union([
rt.strict({
comment: limitedStringSchema({ fieldName: 'comment', min: 1, max: MAX_COMMENT_LENGTH }),
type: rt.literal(CommentType.user),
owner: rt.string,
}),
AlertCommentRequestRt,
rt.strict({
type: rt.literal(CommentType.actions),
comment: limitedStringSchema({ fieldName: 'comment', min: 1, max: MAX_COMMENT_LENGTH }),
actions: rt.strict({
targets: rt.array(
rt.strict({
hostname: rt.string,
endpointId: rt.string,
})
),
type: rt.string,
}),
owner: rt.string,
}),
ExternalReferenceNoSORt,
ExternalReferenceSORt,
PersistableStateAttachmentRt,
]);

export const CommentRequestWithoutRefsRt = rt.union([
BasicCommentRequestRt,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export const MAX_REPORTERS_FILTER_LENGTH = 100 as const;
export const MAX_TITLE_LENGTH = 160 as const;
export const MAX_CATEGORY_LENGTH = 50 as const;
export const MAX_DESCRIPTION_LENGTH = 30000 as const;
export const MAX_COMMENT_LENGTH = 30000 as const;
export const MAX_LENGTH_PER_TAG = 256 as const;
export const MAX_TAGS_PER_CASE = 200 as const;
export const MAX_DELETE_IDS_LENGTH = 100 as const;
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/docs/openapi/bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -5553,6 +5553,7 @@
"comment": {
"description": "The new comment. It is required only when `type` is `user`.",
"type": "string",
"maxLength": 30000,
"example": "A new comment."
},
"owner": {
Expand Down Expand Up @@ -5642,6 +5643,7 @@
"comment": {
"description": "The new comment. It is required only when `type` is `user`.",
"type": "string",
"maxLength": 30000,
"example": "A new comment."
},
"id": {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/docs/openapi/bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3597,6 +3597,7 @@ components:
comment:
description: The new comment. It is required only when `type` is `user`.
type: string
maxLength: 30000
example: A new comment.
owner:
$ref: '#/components/schemas/owners'
Expand Down Expand Up @@ -3663,6 +3664,7 @@ components:
comment:
description: The new comment. It is required only when `type` is `user`.
type: string
maxLength: 30000
example: A new comment.
id:
type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ properties:
comment:
description: The new comment. It is required only when `type` is `user`.
type: string
maxLength: 30000
example: A new comment.
owner:
$ref: 'owners.yaml'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ properties:
comment:
description: The new comment. It is required only when `type` is `user`.
type: string
maxLength: 30000
example: A new comment.
id:
type: string
Expand Down
27 changes: 27 additions & 0 deletions x-pack/plugins/cases/server/client/attachments/add.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { comment } from '../../mocks';
import { createCasesClientMockArgs } from '../mocks';
import { MAX_COMMENT_LENGTH } from '../../../common/constants';
import { addComment } from './add';

describe('addComment', () => {
Expand All @@ -22,4 +23,30 @@ describe('addComment', () => {
addComment({ comment: { ...comment, foo: 'bar' }, caseId: 'test-case' }, clientArgs)
).rejects.toThrow('invalid keys "foo"');
});

it('should throw an error if the comment length is too long', async () => {
const longComment = 'x'.repeat(MAX_COMMENT_LENGTH + 1);

await expect(
addComment({ comment: { ...comment, comment: longComment }, caseId: 'test-case' }, clientArgs)
).rejects.toThrow(
`Failed while adding a comment to case id: test-case error: Error: The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH}.`
);
});

it('should throw an error if the comment is an empty string', async () => {
await expect(
addComment({ comment: { ...comment, comment: '' }, caseId: 'test-case' }, clientArgs)
).rejects.toThrow(
'Failed while adding a comment to case id: test-case error: Error: The comment field cannot be an empty string.'
);
});

it('should throw an error if the description is a string with empty characters', async () => {
await expect(
addComment({ comment: { ...comment, comment: ' ' }, caseId: 'test-case' }, clientArgs)
).rejects.toThrow(
'Failed while adding a comment to case id: test-case error: Error: The comment field cannot be an empty string.'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
* 2.0.
*/

import { comment } from '../../mocks';
import { comment, actionComment } from '../../mocks';
import { createCasesClientMockArgs } from '../mocks';
import { MAX_COMMENT_LENGTH } from '../../../common/constants';
import { bulkCreate } from './bulk_create';

describe('bulkCreate', () => {
Expand All @@ -22,4 +23,79 @@ describe('bulkCreate', () => {
bulkCreate({ attachments: [{ ...comment, foo: 'bar' }], caseId: 'test-case' }, clientArgs)
).rejects.toThrow('invalid keys "foo"');
});

describe('comments', () => {
it('should throw an error if the comment length is too long', async () => {
const longComment = Array(MAX_COMMENT_LENGTH + 1)
.fill('x')
.toString();

await expect(
bulkCreate(
{ attachments: [{ ...comment, comment: longComment }], caseId: 'test-case' },
clientArgs
)
).rejects.toThrow(
`Failed while bulk creating attachment to case id: test-case error: Error: The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH}.`
);
});

it('should throw an error if the comment is an empty string', async () => {
await expect(
bulkCreate({ attachments: [{ ...comment, comment: '' }], caseId: 'test-case' }, clientArgs)
).rejects.toThrow(
'Failed while bulk creating attachment to case id: test-case error: Error: The comment field cannot be an empty string.'
);
});

it('should throw an error if the description is a string with empty characters', async () => {
await expect(
bulkCreate(
{ attachments: [{ ...comment, comment: ' ' }], caseId: 'test-case' },
clientArgs
)
).rejects.toThrow(
'Failed while bulk creating attachment to case id: test-case error: Error: The comment field cannot be an empty string.'
);
});
});

describe('actions', () => {
it('should throw an error if the comment length is too long', async () => {
const longComment = Array(MAX_COMMENT_LENGTH + 1)
.fill('x')
.toString();

await expect(
bulkCreate(
{ attachments: [{ ...actionComment, comment: longComment }], caseId: 'test-case' },
clientArgs
)
).rejects.toThrow(
`Failed while bulk creating attachment to case id: test-case error: Error: The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH}.`
);
});

it('should throw an error if the comment is an empty string', async () => {
await expect(
bulkCreate(
{ attachments: [{ ...actionComment, comment: '' }], caseId: 'test-case' },
clientArgs
)
).rejects.toThrow(
'Failed while bulk creating attachment to case id: test-case error: Error: The comment field cannot be an empty string.'
);
});

it('should throw an error if the description is a string with empty characters', async () => {
await expect(
bulkCreate(
{ attachments: [{ ...actionComment, comment: ' ' }], caseId: 'test-case' },
clientArgs
)
).rejects.toThrow(
'Failed while bulk creating attachment to case id: test-case error: Error: The comment field cannot be an empty string.'
);
});
});
});
Loading

0 comments on commit 8543d5f

Please sign in to comment.