Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ResponseOps] [Cases] Attach file to case API #198377

Merged
merged 24 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9d5de3b
Initial commit.
adcoelho Oct 30, 2024
1b93702
Merge remote-tracking branch 'upstream/main' into add-files-API-cases
adcoelho Oct 30, 2024
d579956
Add documentation.
adcoelho Oct 30, 2024
84e12fe
Little fix in the documentation
adcoelho Oct 30, 2024
9a091a4
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Oct 30, 2024
3b21de1
[CI] Auto-commit changed files from 'make api-docs'
kibanamachine Oct 30, 2024
80a0075
PR comments.
adcoelho Nov 7, 2024
9afd689
[CI] Auto-commit changed files from 'make api-docs'
kibanamachine Nov 7, 2024
ec78640
Fixed a type error.
adcoelho Nov 11, 2024
9b7aeb4
Merge remote-tracking branch 'upstream/main' into add-files-API-cases
adcoelho Nov 11, 2024
17d8a76
PR comments 2.
adcoelho Nov 11, 2024
4054854
Documentation and restructuring types.
adcoelho Nov 11, 2024
0a6a417
Fix types
adcoelho Nov 11, 2024
f4e338b
Reverse docs changes.
adcoelho Nov 12, 2024
e8ad38f
Fix format error; regenerate output
lcawl Nov 12, 2024
d1f3a9a
Merge branch 'main' into add-files-API-cases
lcawl Nov 12, 2024
650289f
Fix bulleted list
lcawl Nov 12, 2024
4d4fc33
Merge branch 'main' into add-files-API-cases
adcoelho Nov 13, 2024
95b00e1
Merge remote-tracking branch 'upstream/main' into add-files-API-cases
adcoelho Nov 13, 2024
7c4b8ff
Merge branch 'main' into add-files-API-cases
adcoelho Nov 13, 2024
801eb27
Fix tests.
adcoelho Nov 14, 2024
7cbb042
Fixed bug uploading big files.
adcoelho Nov 14, 2024
a61160c
Final touches
adcoelho Nov 14, 2024
dab3b9c
[CI] Auto-commit changed files from 'make api-docs'
kibanamachine Nov 14, 2024
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
62 changes: 62 additions & 0 deletions oas_docs/output/kibana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7316,6 +7316,46 @@ paths:
summary: Push a case to an external service
tags:
- cases
/api/cases/{caseId}/files:
post:
description: >
Attach a file to a case. You must have `all` privileges for the
**Cases** feature in the **Management**, **Observability**, or
**Security** section of the Kibana feature privileges, depending on the
owner of the case you're updating. The request must include:

- The `Content-Type: multipart/form-data` HTTP header.

- A link to the file that is being uploaded.
adcoelho marked this conversation as resolved.
Show resolved Hide resolved
operationId: addCaseFileDefaultSpace
parameters:
- $ref: '#/components/parameters/Cases_kbn_xsrf'
- $ref: '#/components/parameters/Cases_case_id'
requestBody:
content:
multipart/form-data; Elastic-Api-Version=2023-10-31:
schema:
$ref: '#/components/schemas/Cases_add_case_file_request'
required: true
responses:
'200':
content:
application/json; Elastic-Api-Version=2023-10-31:
examples:
addCaseFileResponse:
$ref: '#/components/examples/Cases_add_comment_response'
schema:
$ref: '#/components/schemas/Cases_case_response_properties'
description: Indicates a successful call.
'401':
content:
application/json; Elastic-Api-Version=2023-10-31:
schema:
$ref: '#/components/schemas/Cases_4xx_response'
description: Authorization information is missing or invalid.
summary: Attach a file to a case
tags:
- cases
/api/cases/{caseId}/user_actions:
get:
deprecated: true
Expand Down Expand Up @@ -44447,6 +44487,28 @@ components:
- $ref: '#/components/schemas/Cases_add_alert_comment_request_properties'
- $ref: '#/components/schemas/Cases_add_user_comment_request_properties'
title: Add case comment request
Cases_add_case_file_request:
description: >-
Defines the file that will be attached to the case. Optional parameters
will be generated automatically from the file metadata if not defined.
type: object
properties:
file:
description: The file being attached to the case.
format: binary
type: string
filename:
description: >-
The name of file being attached to the case. **This should not
adcoelho marked this conversation as resolved.
Show resolved Hide resolved
include the file extension.**
type: string
mimeType:
adcoelho marked this conversation as resolved.
Show resolved Hide resolved
description: The MIME type of the file being attached to the case.
example: image/jpeg
type: string
required:
- file
title: Add case file request properties
Cases_add_user_comment_request_properties:
description: Defines properties for case comment requests when type is user.
properties:
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/cases/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export const CASE_FIND_USER_ACTIONS_URL = `${CASE_USER_ACTIONS_URL}/_find` as co
export const CASE_ALERTS_URL = `${CASES_URL}/alerts/{alert_id}` as const;
export const CASE_DETAILS_ALERTS_URL = `${CASE_DETAILS_URL}/alerts` as const;

export const CASE_FILES_URL = `${CASE_DETAILS_URL}/files` as const;

/**
* Internal routes
*/
Expand Down Expand Up @@ -139,6 +141,7 @@ export const MAX_TEMPLATE_DESCRIPTION_LENGTH = 1000 as const;
export const MAX_TEMPLATES_LENGTH = 10 as const;
export const MAX_TEMPLATE_TAG_LENGTH = 50 as const;
export const MAX_TAGS_PER_TEMPLATE = 10 as const;
export const MAX_FILENAME_LENGTH = 160 as const;

/**
* Cases features
Expand Down
27 changes: 23 additions & 4 deletions x-pack/plugins/cases/common/schema/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
limitedArraySchema,
limitedNumberSchema,
limitedStringSchema,
mimeTypeString,
NonEmptyString,
paginationSchema,
limitedNumberAsIntegerSchema,
Expand Down Expand Up @@ -321,14 +322,32 @@ describe('schema', () => {
});
});

describe('mimeTypeString', () => {
it('works correctly when the value is an allowed mime type', () => {
expect(PathReporter.report(mimeTypeString.decode('image/jpx'))).toMatchInlineSnapshot(`
Array [
"No errors!",
]
`);
});

it('fails when the value is not an allowed mime type', () => {
expect(PathReporter.report(mimeTypeString.decode('foo/bar'))).toMatchInlineSnapshot(`
Array [
"The mime type field value foo/bar is not allowed.",
]
`);
});
});

describe('limitedNumberAsIntegerSchema', () => {
it('works correctly the number is safe integer', () => {
expect(PathReporter.report(limitedNumberAsIntegerSchema({ fieldName: 'foo' }).decode(1)))
.toMatchInlineSnapshot(`
Array [
"No errors!",
]
`);
Array [
"No errors!",
]
`);
});

it('fails when given a number that is lower than the minimum', () => {
Expand Down
15 changes: 15 additions & 0 deletions x-pack/plugins/cases/common/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { either } from 'fp-ts/lib/Either';
import { MAX_DOCS_PER_PAGE } from '../constants';
import type { PartialPaginationType } from './types';
import { PaginationSchemaRt } from './types';
import { ALLOWED_MIME_TYPES } from '../constants/mime_types';

export interface LimitedSchemaType {
fieldName: string;
Expand Down Expand Up @@ -194,3 +195,17 @@ export const regexStringRt = ({ codec, pattern, message }: RegexStringSchemaType
}),
rt.identity
);

export const mimeTypeString = new rt.Type<string, string, unknown>(
'mimeTypeString',
rt.string.is,
(input, context) =>
either.chain(rt.string.validate(input, context), (s) => {
if (!ALLOWED_MIME_TYPES.includes(s)) {
return rt.failure(input, context, `The mime type field value ${s} is not allowed.`);
}

return rt.success(s);
}),
rt.identity
);
52 changes: 51 additions & 1 deletion x-pack/plugins/cases/common/types/api/attachment/v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
*/

import { PathReporter } from 'io-ts/lib/PathReporter';
import { MAX_BULK_CREATE_ATTACHMENTS, MAX_COMMENT_LENGTH } from '../../../constants';
import {
MAX_BULK_CREATE_ATTACHMENTS,
MAX_COMMENT_LENGTH,
MAX_FILENAME_LENGTH,
} from '../../../constants';
import { AttachmentType } from '../../domain/attachment/v1';
import {
AttachmentPatchRequestRt,
Expand All @@ -17,6 +21,7 @@ import {
BulkGetAttachmentsRequestRt,
BulkGetAttachmentsResponseRt,
FindAttachmentsQueryParamsRt,
PostFileAttachmentRequestRt,
} from './v1';

describe('Attachments', () => {
Expand Down Expand Up @@ -389,4 +394,49 @@ describe('Attachments', () => {
});
});
});

describe('PostFileAttachmentRequestRt', () => {
const defaultRequest = {
file: 'Solve this fast!',
filename: 'filename',
};

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

expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});

it('removes foo:bar attributes from request', () => {
const query = PostFileAttachmentRequestRt.decode({ ...defaultRequest, foo: 'bar' });

expect(query).toStrictEqual({
_tag: 'Right',
right: defaultRequest,
});
});

describe('errors', () => {
it('throws an error when the filename is too long', () => {
const longFilename = 'x'.repeat(MAX_FILENAME_LENGTH + 1);

expect(
PathReporter.report(
PostFileAttachmentRequestRt.decode({ ...defaultRequest, filename: longFilename })
)
).toContain('The length of the filename is too long. The maximum length is 160.');
});

it('throws an error when the filename is too small', () => {
expect(
PathReporter.report(
PostFileAttachmentRequestRt.decode({ ...defaultRequest, filename: '' })
)
).toContain('The filename field cannot be an empty string.');
});
});
});
});
17 changes: 17 additions & 0 deletions x-pack/plugins/cases/common/types/api/attachment/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
MAX_COMMENTS_PER_PAGE,
MAX_COMMENT_LENGTH,
MAX_DELETE_FILES,
MAX_FILENAME_LENGTH,
} from '../../../constants';
import {
limitedArraySchema,
Expand Down Expand Up @@ -47,7 +48,23 @@ export const BulkDeleteFileAttachmentsRequestRt = rt.strict({
}),
});

export const PostFileAttachmentRequestRt = rt.intersection([
rt.strict({
file: rt.unknown,
}),
rt.exact(
rt.partial({
filename: limitedStringSchema({ fieldName: 'filename', min: 1, max: MAX_FILENAME_LENGTH }),
})
),
]);

export type BulkDeleteFileAttachmentsRequest = rt.TypeOf<typeof BulkDeleteFileAttachmentsRequestRt>;
export type PostFileAttachmentRequest = rt.TypeOf<typeof PostFileAttachmentRequestRt>;

/**
* Attachments
*/

const BasicAttachmentRequestRt = rt.union([
UserCommentAttachmentPayloadRt,
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/cases/common/types/domain/attachment/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
*/

import * as rt from 'io-ts';
import { limitedStringSchema, mimeTypeString } from '../../../schema';
import { jsonValueRt } from '../../../api';
import { UserRt } from '../user/v1';
import { MAX_FILENAME_LENGTH } from '../../../constants';

/**
* Files
Expand Down Expand Up @@ -35,6 +37,12 @@ export const AttachmentAttributesBasicRt = rt.strict({
updated_by: rt.union([UserRt, rt.null]),
});

export const FileAttachmentPayloadRt = rt.strict({
file: rt.unknown,
mimeType: mimeTypeString,
filename: limitedStringSchema({ fieldName: 'filename', min: 1, max: MAX_FILENAME_LENGTH }),
});

/**
* User comment
*/
Expand Down
79 changes: 79 additions & 0 deletions x-pack/plugins/cases/docs/openapi/bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,61 @@
}
}
}
},
"/api/cases/{caseId}/files": {
"post": {
"summary": "Attach a file to a case",
"operationId": "addCaseFileDefaultSpace",
"description": "Attach a file to a case. You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. The request must include:\n- The `Content-Type: multipart/form-data` HTTP header.\n- A link to the file that is being uploaded.\n",
"tags": [
"cases"
],
"parameters": [
{
"$ref": "#/components/parameters/kbn_xsrf"
},
{
"$ref": "#/components/parameters/case_id"
}
],
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/add_case_file_request"
}
}
}
},
"responses": {
"200": {
"description": "Indicates a successful call.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/case_response_properties"
},
"examples": {
"addCaseFileResponse": {
"$ref": "#/components/examples/add_comment_response"
}
}
}
}
},
"401": {
"description": "Authorization information is missing or invalid.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/4xx_response"
}
}
}
}
}
}
}
},
"components": {
Expand Down Expand Up @@ -4798,6 +4853,30 @@
"example": "create_case"
}
}
},
"add_case_file_request": {
"title": "Add case file request properties",
"required": [
"file"
],
"description": "Defines the file that will be attached to the case. Optional parameters will be generated automatically from the file metadata if not defined.",
"type": "object",
"properties": {
"file": {
"description": "The file being attached to the case.",
"type": "string",
"format": "binary"
},
"filename": {
"description": "The name of file being attached to the case. **This should not include the file extension.**",
"type": "string"
},
"mimeType": {
"description": "The MIME type of the file being attached to the case.",
"type": "string",
"example": "image/jpeg"
}
}
}
},
"examples": {
Expand Down
Loading