Skip to content

Commit

Permalink
Fix bulleted list
Browse files Browse the repository at this point in the history
  • Loading branch information
lcawl authored and adcoelho committed Nov 13, 2024
1 parent d1f3a9a commit 650289f
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 60 deletions.
6 changes: 4 additions & 2 deletions oas_docs/output/kibana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7323,8 +7323,10 @@ paths:
**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.

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

- A link to the file that is being uploaded.
operationId: addCaseFileDefaultSpace
parameters:
- $ref: '#/components/parameters/Cases_kbn_xsrf'
Expand Down
9 changes: 0 additions & 9 deletions x-pack/plugins/cases/common/types/api/attachment/v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,6 @@ describe('Attachments', () => {
const defaultRequest = {
file: 'Solve this fast!',
filename: 'filename',
mimeType: 'image/jpeg',
};

it('has the expected attributes in request', () => {
Expand Down Expand Up @@ -438,14 +437,6 @@ describe('Attachments', () => {
)
).toContain('The filename field cannot be an empty string.');
});

it('throws an error when the mimeType is not allowed', () => {
expect(
PathReporter.report(
PostFileAttachmentRequestRt.decode({ ...defaultRequest, mimeType: 'foo/bar' })
)
).toContain('The mime type field value foo/bar is not allowed.');
});
});
});
});
2 changes: 0 additions & 2 deletions x-pack/plugins/cases/common/types/api/attachment/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
import {
limitedArraySchema,
limitedStringSchema,
mimeTypeString,
NonEmptyString,
paginationSchema,
} from '../../../schema';
Expand Down Expand Up @@ -55,7 +54,6 @@ export const PostFileAttachmentRequestRt = rt.intersection([
}),
rt.exact(
rt.partial({
mimeType: mimeTypeString,
filename: limitedStringSchema({ fieldName: 'filename', min: 1, max: MAX_FILENAME_LENGTH }),
})
),
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/docs/openapi/bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -1874,7 +1874,7 @@
"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",
"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"
],
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/cases/docs/openapi/bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1214,8 +1214,8 @@ paths:
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:
- The `Content-Type: multipart/form-data` HTTP header.
- A link to the file that is being uploaded.
- The `Content-Type: multipart/form-data` HTTP header.
- A link to the file that is being uploaded.
tags:
- cases
parameters:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@ properties:
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ post:
**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.
- The `Content-Type: multipart/form-data` HTTP header.
- A link to the file that is being uploaded.
tags:
- cases
parameters:
Expand Down
7 changes: 2 additions & 5 deletions x-pack/plugins/cases/server/client/attachments/add_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import { SavedObjectsUtils } from '@kbn/core/server';

import type { Readable } from 'stream';
import type { Owner } from '../../../common/constants/types';
import { FileAttachmentPayloadRt, type Case } from '../../../common/types/domain';
import type { CasesClient, CasesClientArgs } from '..';
Expand All @@ -19,13 +18,11 @@ import { validateMaxUserActions } from '../../common/validators';
import { constructFileKindIdByOwner } from '../../../common/files';
import { Operations } from '../../authorization';
import { validateRegisteredAttachments } from './validators';
import { decodeWithExcessOrThrow } from '../../common/runtime_types';
import { buildAttachmentRequestFromFileJSON } from '../utils';
import { decodeWithExcessOrThrow } from '../../common/runtime_types';

/**
* Create a file attachment to a case.
*
* @ignore
*/
export const addFile = async (
addFileArgs: AddFileArgs,
Expand Down Expand Up @@ -75,7 +72,7 @@ export const addFile = async (
meta: { caseIds: [caseId], owner: [owner] },
});

await createdFile.uploadContent(file as Readable, $abort);
await createdFile.uploadContent(file, $abort);

const commentReq = buildAttachmentRequestFromFileJSON({
owner,
Expand Down
25 changes: 12 additions & 13 deletions x-pack/plugins/cases/server/routes/api/files/post_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { AbortedUploadError } from '@kbn/files-plugin/server/file/errors';
import type { HapiReadableStream } from '../../../client/attachments/types';
import type { attachmentApiV1 } from '../../../../common/types/api';

import { CASE_FILES_URL, MAX_FILE_SIZE } from '../../../../common/constants';
import { CASE_FILES_URL } from '../../../../common/constants';
import { createCaseError } from '../../../common/error';
import { createCasesRoute } from '../create_cases_route';
import type { caseDomainV1 } from '../../../../common/types/domain';
Expand All @@ -34,13 +34,6 @@ export const postFileRoute = createCasesRoute({
summary: 'Attach a file to a case',
tags: ['oas-tag:cases'],
body: {
/*
* The maximum file size allowed here is the upper limit allowed when registering the cases file
* kinds. We register the file kinds in x-pack/plugins/cases/public/files/index.ts.
*
* If the user configured a custom value the validation will still fail when calling the service.
*/
maxBytes: MAX_FILE_SIZE,
output: 'stream',
parse: true,
accepts: 'multipart/form-data',
Expand All @@ -57,14 +50,20 @@ export const postFileRoute = createCasesRoute({
const fileRequest = request.body as attachmentApiV1.PostFileAttachmentRequest;
const file = fileRequest.file as HapiReadableStream;

const fileExtension = parse(file.hapi.filename).ext.toLowerCase();
const metadataMimeType = mime.lookup(fileExtension) || '';
const metadataFilename = parse(file.hapi.filename).name;
let filename = fileRequest.filename;
let mimeType;

if (file.hapi != null) {
const parsedFilename = parse(file.hapi.filename);

filename ??= parsedFilename.name;
mimeType = mime.lookup(parsedFilename.ext.toLowerCase()) || undefined;
}

const res: caseDomainV1.Case = await casesClient.attachments.addFile({
file,
filename: fileRequest.filename ?? metadataFilename,
mimeType: fileRequest.mimeType ?? metadataMimeType,
filename: filename ?? '',
mimeType,
caseId: request.params.case_id,
$abort,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ export const createFileAttachment = async ({
const { body: theCase } = await apiCall
.set('kbn-xsrf', 'true')
.set(headers)
.field('file', params.file as string)
.field('filename', params.filename ?? '')
.field('mimeType', params.mimeType ?? '')
.attach('file', Buffer.from(params.file as unknown as string), params.filename)
.expect(expectedHttpCode);

return theCase;
Expand Down
5 changes: 2 additions & 3 deletions x-pack/test/cases_api_integration/common/lib/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ export const postCommentUserReq: UserCommentAttachmentPayload = {
};

export const postFileReq: PostFileAttachmentRequest = {
file: 'This is a file, its a readable, its ok.',
filename: 'foobar',
mimeType: 'text/plain',
file: 'This is a file, a buffer will be created from this string.',
filename: 'foobar.txt',
};

export const postCommentAlertReq: AlertAttachmentPayload = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
import expect from '@kbn/expect';
import { ExternalReferenceAttachmentAttributes } from '@kbn/cases-plugin/common/types/domain';
import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common';
import {
globalRead,
noKibanaPrivileges,
obsOnly,
obsOnlyRead,
obsSecRead,
secOnly,
secOnlyRead,
superUser,
} from '../../../../common/lib/authentication/users';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';

import { defaultUser, postCaseReq, postFileReq } from '../../../../common/lib/mock';
Expand All @@ -17,13 +27,15 @@ import {
removeServerGeneratedPropertiesFromSavedObject,
getAuthWithSuperUser,
deleteAllCaseItems,
superUserSpace1Auth,
} from '../../../../common/lib/api';

// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
const authSpace1 = getAuthWithSuperUser();
const caseRequest = { ...postCaseReq, owner: SECURITY_SOLUTION_OWNER };

describe('post_file', () => {
afterEach(async () => {
Expand All @@ -32,12 +44,7 @@ export default ({ getService }: FtrProviderContext): void => {

describe('happy path', () => {
it('should post a file in space1', async () => {
const postedCase = await createCase(
supertest,
{ ...postCaseReq, owner: SECURITY_SOLUTION_OWNER },
200,
authSpace1
);
const postedCase = await createCase(supertest, caseRequest, 200, authSpace1);
const patchedCase = await createFileAttachment({
supertest,
caseId: postedCase.id,
Expand All @@ -58,8 +65,8 @@ export default ({ getService }: FtrProviderContext): void => {
soType: 'file',
type: 'savedObject',
});
expect(fileMetadata.name).to.be(postFileReq.filename);
expect(fileMetadata.mimeType).to.be(postFileReq.mimeType);
expect(fileMetadata.name).to.be('foobar');
expect(fileMetadata.mimeType).to.be('text/plain');
expect(fileMetadata.extension).to.be('txt');

// updates the case correctly after adding a comment
Expand All @@ -70,7 +77,7 @@ export default ({ getService }: FtrProviderContext): void => {

describe('unhappy path', () => {
it('should return a 400 when posting a file without a filename', async () => {
const postedCase = await createCase(supertest, postCaseReq, 200, authSpace1);
const postedCase = await createCase(supertest, caseRequest, 200, authSpace1);
await createFileAttachment({
supertest,
caseId: postedCase.id,
Expand All @@ -83,7 +90,7 @@ export default ({ getService }: FtrProviderContext): void => {
it('should return a 400 when posting a file with a filename that is too long', async () => {
const longFilename = Array(161).fill('a').toString();

const postedCase = await createCase(supertest, postCaseReq, 200, authSpace1);
const postedCase = await createCase(supertest, caseRequest, 200, authSpace1);
await createFileAttachment({
supertest,
caseId: postedCase.id,
Expand All @@ -94,15 +101,71 @@ export default ({ getService }: FtrProviderContext): void => {
});

it('should return a 400 when posting a file with an invalid mime type', async () => {
const postedCase = await createCase(supertest, postCaseReq, 200, authSpace1);
const postedCase = await createCase(supertest, caseRequest, 200, authSpace1);
await createFileAttachment({
supertest,
caseId: postedCase.id,
params: { ...postFileReq, mimeType: 'foo/bar' },
params: { ...postFileReq, filename: 'foobar.124zas' },
auth: authSpace1,
expectedHttpCode: 400,
});
});
});

describe('rbac', () => {
const supertestWithoutAuth = getService('supertestWithoutAuth');

afterEach(async () => {
await deleteAllCaseItems(es);
});

it('should not post a file when the user does not have permissions for that owner', async () => {
const postedCase = await createCase(supertest, caseRequest, 200, authSpace1);

await createFileAttachment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postFileReq,
auth: { user: obsOnly, space: 'space1' },
expectedHttpCode: 403,
});
});

for (const user of [globalRead, secOnlyRead, obsOnlyRead, obsSecRead, noKibanaPrivileges]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should not post a file`, async () => {
const postedCase = await createCase(
supertestWithoutAuth,
caseRequest,
200,
superUserSpace1Auth
);

await createFileAttachment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postFileReq,
auth: { user, space: 'space1' },
expectedHttpCode: 403,
});
});
}

it('should not post a file in a space the user does not have permissions for', async () => {
const postedCase = await createCase(supertestWithoutAuth, caseRequest, 200, {
user: superUser,
space: 'space2',
});

await createFileAttachment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postFileReq,
auth: { user: secOnly, space: 'space2' },
expectedHttpCode: 403,
});
});
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ export default ({ getService }: FtrProviderContext): void => {
soType: 'file',
type: 'savedObject',
});
expect(fileMetadata.name).to.be(postFileReq.filename);
expect(fileMetadata.mimeType).to.be(postFileReq.mimeType);
expect(fileMetadata.name).to.be('foobar');
expect(fileMetadata.mimeType).to.be('text/plain');
expect(fileMetadata.extension).to.be('txt');

// updates the case correctly after adding a comment
Expand Down

0 comments on commit 650289f

Please sign in to comment.