Skip to content

Commit 234d48d

Browse files
[Cases] Adding files configuration fields (#154013)
Fixes: #151935 This PR allows the mime types and max file size for the files functionality within cases to be configured through the kibana.yml. We set the defaults maxSize to be 100 mb and if it is not set by the user we also restrict images to be 10 mb. If the `maxSize` is set by the user we use it for all mime types including images (or whatever the user has specified in `allowedMimeTypes`). The file service changes are just mocks to help with testing some of the configuration options. New fields ``` { files: { allowedMimeTypes: string[] maxSize: positive number (minimum 0) <-- exposed to the browser } } ``` ## Release Notes Cases added two configuration options to allow users to control which files mime types are allowed to be attached to cases and the approved max size of a file being upload. `xpack.cases.files.allowedMimeTypes` - An array of strings representing the allowed mime types to be attached to a case. `xpack.cases.files.maxSize` - A number representing the file size limit for files being attached to a case (in bytes).
1 parent 210a7eb commit 234d48d

File tree

18 files changed

+1065
-36
lines changed

18 files changed

+1065
-36
lines changed

src/plugins/files/public/mocks.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,25 @@
88

99
import { createMockFilesClient as createBaseMocksFilesClient } from '@kbn/shared-ux-file-mocks';
1010
import type { DeeplyMockedKeys } from '@kbn/utility-types-jest';
11-
import type { FilesClient } from './types';
11+
import { FilesSetup } from '.';
12+
import type { FilesClient, FilesClientFactory } from './types';
1213

1314
export const createMockFilesClient = (): DeeplyMockedKeys<FilesClient> => ({
1415
...createBaseMocksFilesClient(),
1516
getMetrics: jest.fn(),
1617
publicDownload: jest.fn(),
1718
});
19+
20+
export const createMockFilesSetup = (): DeeplyMockedKeys<FilesSetup> => {
21+
return {
22+
filesClientFactory: createMockFilesClientFactory(),
23+
registerFileKind: jest.fn(),
24+
};
25+
};
26+
27+
export const createMockFilesClientFactory = (): DeeplyMockedKeys<FilesClientFactory> => {
28+
return {
29+
asScoped: jest.fn(),
30+
asUnscoped: jest.fn(),
31+
};
32+
};

src/plugins/files/server/mocks.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { KibanaRequest } from '@kbn/core/server';
1010
import { DeeplyMockedKeys } from '@kbn/utility-types-jest';
1111
import * as stream from 'stream';
1212
import { File } from '../common';
13-
import { FileClient, FileServiceFactory, FileServiceStart } from '.';
13+
import { FileClient, FileServiceFactory, FileServiceStart, FilesSetup } from '.';
1414

1515
export const createFileServiceMock = (): DeeplyMockedKeys<FileServiceStart> => ({
1616
create: jest.fn(),
@@ -78,3 +78,9 @@ export const createFileClientMock = (): DeeplyMockedKeys<FileClient> => {
7878
listShares: jest.fn().mockResolvedValue({ shares: [] }),
7979
};
8080
};
81+
82+
export const createFilesSetupMock = (): DeeplyMockedKeys<FilesSetup> => {
83+
return {
84+
registerFileKind: jest.fn(),
85+
};
86+
};

test/plugin_functional/test_suites/core_plugins/rendering.ts

+2
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
165165
'xpack.apm.serviceMapEnabled (boolean)',
166166
'xpack.apm.ui.enabled (boolean)',
167167
'xpack.apm.ui.maxTraceItems (number)',
168+
'xpack.cases.files.allowedMimeTypes (array)',
169+
'xpack.cases.files.maxSize (number)',
168170
'xpack.cases.markdownPlugins.lens (boolean)',
169171
'xpack.ccr.ui.enabled (boolean)',
170172
'xpack.cloud.base_url (string)',

x-pack/plugins/cases/common/constants/files.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
*/
77

88
export const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MiB
9+
export const MAX_IMAGE_FILE_SIZE = 10 * 1024 * 1024; // 10 MiB
910
export const MAX_FILES_PER_CASE = 100;
1011
export const MAX_DELETE_FILES = 50;

x-pack/plugins/cases/common/constants/owners.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const SECURITY_SOLUTION_OWNER = 'securitySolution' as const;
1515
export const OBSERVABILITY_OWNER = 'observability' as const;
1616
export const GENERAL_CASES_OWNER = APP_ID;
1717

18-
export const OWNERS = [SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER, GENERAL_CASES_OWNER] as const;
18+
export const OWNERS = [GENERAL_CASES_OWNER, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER] as const;
1919

2020
interface RouteInfo {
2121
id: Owner;

x-pack/plugins/cases/common/ui/types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ export interface CasesUiConfigType {
5252
markdownPlugins: {
5353
lens: boolean;
5454
};
55+
files: {
56+
maxSize?: number;
57+
allowedMimeTypes: string[];
58+
};
5559
}
5660

5761
export const StatusAll = 'all' as const;

x-pack/plugins/cases/public/common/lib/kibana/services.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ export class KibanaServices {
1919
http,
2020
kibanaVersion,
2121
config,
22-
}: GlobalServices & { kibanaVersion: string; config: CasesUiConfigType }) {
22+
}: GlobalServices & {
23+
kibanaVersion: string;
24+
config: CasesUiConfigType;
25+
}) {
2326
this.services = { http };
2427
this.kibanaVersion = kibanaVersion;
2528
this.config = config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { MAX_FILE_SIZE } from '../../common/constants';
9+
import { createMockFilesSetup } from '@kbn/files-plugin/public/mocks';
10+
import { registerCaseFileKinds } from '.';
11+
import type { FilesConfig } from './types';
12+
13+
describe('ui files index', () => {
14+
describe('registerCaseFileKinds', () => {
15+
const mockFilesSetup = createMockFilesSetup();
16+
17+
beforeEach(() => {
18+
jest.clearAllMocks();
19+
});
20+
21+
describe('allowedMimeTypes', () => {
22+
const config: FilesConfig = {
23+
allowedMimeTypes: ['abc'],
24+
maxSize: undefined,
25+
};
26+
27+
beforeEach(() => {
28+
registerCaseFileKinds(config, mockFilesSetup);
29+
});
30+
31+
it('sets cases allowed mime types to abc', () => {
32+
expect(mockFilesSetup.registerFileKind.mock.calls[0][0].allowedMimeTypes).toEqual(['abc']);
33+
});
34+
35+
it('sets observability allowed mime types to abc', () => {
36+
expect(mockFilesSetup.registerFileKind.mock.calls[1][0].allowedMimeTypes).toEqual(['abc']);
37+
});
38+
39+
it('sets securitySolution allowed mime types to 100 mb', () => {
40+
expect(mockFilesSetup.registerFileKind.mock.calls[2][0].allowedMimeTypes).toEqual(['abc']);
41+
});
42+
});
43+
44+
describe('max file size', () => {
45+
describe('default max file size', () => {
46+
const config: FilesConfig = {
47+
allowedMimeTypes: [],
48+
maxSize: undefined,
49+
};
50+
51+
beforeEach(() => {
52+
registerCaseFileKinds(config, mockFilesSetup);
53+
});
54+
55+
it('sets cases max file size to 100 mb', () => {
56+
expect(mockFilesSetup.registerFileKind.mock.calls[0][0].maxSizeBytes).toEqual(
57+
MAX_FILE_SIZE
58+
);
59+
});
60+
61+
it('sets observability max file size to 100 mb', () => {
62+
expect(mockFilesSetup.registerFileKind.mock.calls[1][0].maxSizeBytes).toEqual(
63+
MAX_FILE_SIZE
64+
);
65+
});
66+
67+
it('sets securitySolution max file size to 100 mb', () => {
68+
expect(mockFilesSetup.registerFileKind.mock.calls[2][0].maxSizeBytes).toEqual(
69+
MAX_FILE_SIZE
70+
);
71+
});
72+
});
73+
74+
describe('custom file size', () => {
75+
const config: FilesConfig = {
76+
allowedMimeTypes: [],
77+
maxSize: 5,
78+
};
79+
80+
beforeEach(() => {
81+
registerCaseFileKinds(config, mockFilesSetup);
82+
});
83+
84+
it('sets cases max file size to 5', () => {
85+
expect(mockFilesSetup.registerFileKind.mock.calls[0][0].maxSizeBytes).toEqual(5);
86+
});
87+
88+
it('sets observability max file size to 5', () => {
89+
expect(mockFilesSetup.registerFileKind.mock.calls[1][0].maxSizeBytes).toEqual(5);
90+
});
91+
92+
it('sets securitySolution max file size to 5', () => {
93+
expect(mockFilesSetup.registerFileKind.mock.calls[2][0].maxSizeBytes).toEqual(5);
94+
});
95+
});
96+
});
97+
});
98+
});

x-pack/plugins/cases/public/files/index.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,36 @@
77

88
import type { FilesSetup } from '@kbn/files-plugin/public';
99
import type { FileKindBrowser } from '@kbn/shared-ux-file-types';
10-
import { ALLOWED_MIME_TYPES } from '../../common/constants/mime_types';
11-
import { MAX_FILE_SIZE } from '../../common/constants';
10+
import { MAX_FILE_SIZE, OWNERS } from '../../common/constants';
1211
import type { Owner } from '../../common/constants/types';
13-
import { APP_ID, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../common';
1412
import { constructFileKindIdByOwner } from '../../common/files';
13+
import type { CaseFileKinds, FilesConfig } from './types';
1514

16-
const buildFileKind = (owner: Owner): FileKindBrowser => {
15+
const buildFileKind = (config: FilesConfig, owner: Owner): FileKindBrowser => {
1716
return {
1817
id: constructFileKindIdByOwner(owner),
19-
allowedMimeTypes: ALLOWED_MIME_TYPES,
20-
maxSizeBytes: MAX_FILE_SIZE,
18+
allowedMimeTypes: config.allowedMimeTypes,
19+
maxSizeBytes: config.maxSize ?? MAX_FILE_SIZE,
2120
};
2221
};
2322

2423
/**
2524
* The file kind definition for interacting with the file service for the UI
2625
*/
27-
const CASES_FILE_KINDS: Record<Owner, FileKindBrowser> = {
28-
[APP_ID]: buildFileKind(APP_ID),
29-
[SECURITY_SOLUTION_OWNER]: buildFileKind(SECURITY_SOLUTION_OWNER),
30-
[OBSERVABILITY_OWNER]: buildFileKind(OBSERVABILITY_OWNER),
26+
const createFileKinds = (config: FilesConfig): CaseFileKinds => {
27+
const caseFileKinds = new Map<Owner, FileKindBrowser>();
28+
29+
for (const owner of OWNERS) {
30+
caseFileKinds.set(owner, buildFileKind(config, owner));
31+
}
32+
33+
return caseFileKinds;
3134
};
3235

33-
export const registerCaseFileKinds = (filesSetupPlugin: FilesSetup) => {
34-
for (const fileKind of Object.values(CASES_FILE_KINDS)) {
36+
export const registerCaseFileKinds = (config: FilesConfig, filesSetupPlugin: FilesSetup) => {
37+
const fileKinds = createFileKinds(config);
38+
39+
for (const fileKind of fileKinds.values()) {
3540
filesSetupPlugin.registerFileKind(fileKind);
3641
}
3742
};
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { FileKindBrowser } from '@kbn/shared-ux-file-types';
9+
import type { Owner } from '../../common/constants/types';
10+
import type { CasesUiConfigType } from '../containers/types';
11+
12+
export type FilesConfig = CasesUiConfigType['files'];
13+
14+
export type CaseFileKinds = Map<Owner, FileKindBrowser>;

x-pack/plugins/cases/public/plugin.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export class CasesUiPlugin
5353
const externalReferenceAttachmentTypeRegistry = this.externalReferenceAttachmentTypeRegistry;
5454
const persistableStateAttachmentTypeRegistry = this.persistableStateAttachmentTypeRegistry;
5555

56-
registerCaseFileKinds(plugins.files);
56+
const config = this.initializerContext.config.get<CasesUiConfigType>();
57+
registerCaseFileKinds(config.files, plugins.files);
5758

5859
if (plugins.home) {
5960
plugins.home.featureCatalogue.register({
@@ -106,7 +107,13 @@ export class CasesUiPlugin
106107

107108
public start(core: CoreStart, plugins: CasesPluginStart): CasesUiStart {
108109
const config = this.initializerContext.config.get<CasesUiConfigType>();
109-
KibanaServices.init({ ...core, ...plugins, kibanaVersion: this.kibanaVersion, config });
110+
111+
KibanaServices.init({
112+
...core,
113+
...plugins,
114+
kibanaVersion: this.kibanaVersion,
115+
config,
116+
});
110117

111118
/**
112119
* getCasesContextLazy returns a new component each time is being called. To avoid re-renders
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { ConfigSchema } from './config';
9+
10+
describe('config validation', () => {
11+
describe('defaults', () => {
12+
it('sets the defaults correctly', () => {
13+
expect(ConfigSchema.validate({})).toMatchInlineSnapshot(`
14+
Object {
15+
"files": Object {
16+
"allowedMimeTypes": Array [
17+
"image/aces",
18+
"image/apng",
19+
"image/avci",
20+
"image/avcs",
21+
"image/avif",
22+
"image/bmp",
23+
"image/cgm",
24+
"image/dicom-rle",
25+
"image/dpx",
26+
"image/emf",
27+
"image/example",
28+
"image/fits",
29+
"image/g3fax",
30+
"image/heic",
31+
"image/heic-sequence",
32+
"image/heif",
33+
"image/heif-sequence",
34+
"image/hej2k",
35+
"image/hsj2",
36+
"image/jls",
37+
"image/jp2",
38+
"image/jpeg",
39+
"image/jph",
40+
"image/jphc",
41+
"image/jpm",
42+
"image/jpx",
43+
"image/jxr",
44+
"image/jxrA",
45+
"image/jxrS",
46+
"image/jxs",
47+
"image/jxsc",
48+
"image/jxsi",
49+
"image/jxss",
50+
"image/ktx",
51+
"image/ktx2",
52+
"image/naplps",
53+
"image/png",
54+
"image/prs.btif",
55+
"image/prs.pti",
56+
"image/pwg-raster",
57+
"image/svg+xml",
58+
"image/t38",
59+
"image/tiff",
60+
"image/tiff-fx",
61+
"image/vnd.adobe.photoshop",
62+
"image/vnd.airzip.accelerator.azv",
63+
"image/vnd.cns.inf2",
64+
"image/vnd.dece.graphic",
65+
"image/vnd.djvu",
66+
"image/vnd.dwg",
67+
"image/vnd.dxf",
68+
"image/vnd.dvb.subtitle",
69+
"image/vnd.fastbidsheet",
70+
"image/vnd.fpx",
71+
"image/vnd.fst",
72+
"image/vnd.fujixerox.edmics-mmr",
73+
"image/vnd.fujixerox.edmics-rlc",
74+
"image/vnd.globalgraphics.pgb",
75+
"image/vnd.microsoft.icon",
76+
"image/vnd.mix",
77+
"image/vnd.ms-modi",
78+
"image/vnd.mozilla.apng",
79+
"image/vnd.net-fpx",
80+
"image/vnd.pco.b16",
81+
"image/vnd.radiance",
82+
"image/vnd.sealed.png",
83+
"image/vnd.sealedmedia.softseal.gif",
84+
"image/vnd.sealedmedia.softseal.jpg",
85+
"image/vnd.svf",
86+
"image/vnd.tencent.tap",
87+
"image/vnd.valve.source.texture",
88+
"image/vnd.wap.wbmp",
89+
"image/vnd.xiff",
90+
"image/vnd.zbrush.pcx",
91+
"image/webp",
92+
"image/wmf",
93+
"text/plain",
94+
"text/csv",
95+
"text/json",
96+
"application/json",
97+
"application/zip",
98+
"application/gzip",
99+
"application/x-bzip",
100+
"application/x-bzip2",
101+
"application/x-7z-compressed",
102+
"application/x-tar",
103+
"application/pdf",
104+
],
105+
},
106+
"markdownPlugins": Object {
107+
"lens": true,
108+
},
109+
}
110+
`);
111+
});
112+
});
113+
});

0 commit comments

Comments
 (0)