Skip to content

Commit

Permalink
[Cases] Adding files configuration fields (#154013)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
jonathan-buttner authored Apr 3, 2023
1 parent 210a7eb commit 234d48d
Show file tree
Hide file tree
Showing 18 changed files with 1,065 additions and 36 deletions.
17 changes: 16 additions & 1 deletion src/plugins/files/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,25 @@

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

export const createMockFilesClient = (): DeeplyMockedKeys<FilesClient> => ({
...createBaseMocksFilesClient(),
getMetrics: jest.fn(),
publicDownload: jest.fn(),
});

export const createMockFilesSetup = (): DeeplyMockedKeys<FilesSetup> => {
return {
filesClientFactory: createMockFilesClientFactory(),
registerFileKind: jest.fn(),
};
};

export const createMockFilesClientFactory = (): DeeplyMockedKeys<FilesClientFactory> => {
return {
asScoped: jest.fn(),
asUnscoped: jest.fn(),
};
};
8 changes: 7 additions & 1 deletion src/plugins/files/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { KibanaRequest } from '@kbn/core/server';
import { DeeplyMockedKeys } from '@kbn/utility-types-jest';
import * as stream from 'stream';
import { File } from '../common';
import { FileClient, FileServiceFactory, FileServiceStart } from '.';
import { FileClient, FileServiceFactory, FileServiceStart, FilesSetup } from '.';

export const createFileServiceMock = (): DeeplyMockedKeys<FileServiceStart> => ({
create: jest.fn(),
Expand Down Expand Up @@ -78,3 +78,9 @@ export const createFileClientMock = (): DeeplyMockedKeys<FileClient> => {
listShares: jest.fn().mockResolvedValue({ shares: [] }),
};
};

export const createFilesSetupMock = (): DeeplyMockedKeys<FilesSetup> => {
return {
registerFileKind: jest.fn(),
};
};
2 changes: 2 additions & 0 deletions test/plugin_functional/test_suites/core_plugins/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.apm.serviceMapEnabled (boolean)',
'xpack.apm.ui.enabled (boolean)',
'xpack.apm.ui.maxTraceItems (number)',
'xpack.cases.files.allowedMimeTypes (array)',
'xpack.cases.files.maxSize (number)',
'xpack.cases.markdownPlugins.lens (boolean)',
'xpack.ccr.ui.enabled (boolean)',
'xpack.cloud.base_url (string)',
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/constants/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
*/

export const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MiB
export const MAX_IMAGE_FILE_SIZE = 10 * 1024 * 1024; // 10 MiB
export const MAX_FILES_PER_CASE = 100;
export const MAX_DELETE_FILES = 50;
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/common/constants/owners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const SECURITY_SOLUTION_OWNER = 'securitySolution' as const;
export const OBSERVABILITY_OWNER = 'observability' as const;
export const GENERAL_CASES_OWNER = APP_ID;

export const OWNERS = [SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER, GENERAL_CASES_OWNER] as const;
export const OWNERS = [GENERAL_CASES_OWNER, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER] as const;

interface RouteInfo {
id: Owner;
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export interface CasesUiConfigType {
markdownPlugins: {
lens: boolean;
};
files: {
maxSize?: number;
allowedMimeTypes: string[];
};
}

export const StatusAll = 'all' as const;
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/cases/public/common/lib/kibana/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export class KibanaServices {
http,
kibanaVersion,
config,
}: GlobalServices & { kibanaVersion: string; config: CasesUiConfigType }) {
}: GlobalServices & {
kibanaVersion: string;
config: CasesUiConfigType;
}) {
this.services = { http };
this.kibanaVersion = kibanaVersion;
this.config = config;
Expand Down
98 changes: 98 additions & 0 deletions x-pack/plugins/cases/public/files/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { MAX_FILE_SIZE } from '../../common/constants';
import { createMockFilesSetup } from '@kbn/files-plugin/public/mocks';
import { registerCaseFileKinds } from '.';
import type { FilesConfig } from './types';

describe('ui files index', () => {
describe('registerCaseFileKinds', () => {
const mockFilesSetup = createMockFilesSetup();

beforeEach(() => {
jest.clearAllMocks();
});

describe('allowedMimeTypes', () => {
const config: FilesConfig = {
allowedMimeTypes: ['abc'],
maxSize: undefined,
};

beforeEach(() => {
registerCaseFileKinds(config, mockFilesSetup);
});

it('sets cases allowed mime types to abc', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[0][0].allowedMimeTypes).toEqual(['abc']);
});

it('sets observability allowed mime types to abc', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[1][0].allowedMimeTypes).toEqual(['abc']);
});

it('sets securitySolution allowed mime types to 100 mb', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[2][0].allowedMimeTypes).toEqual(['abc']);
});
});

describe('max file size', () => {
describe('default max file size', () => {
const config: FilesConfig = {
allowedMimeTypes: [],
maxSize: undefined,
};

beforeEach(() => {
registerCaseFileKinds(config, mockFilesSetup);
});

it('sets cases max file size to 100 mb', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[0][0].maxSizeBytes).toEqual(
MAX_FILE_SIZE
);
});

it('sets observability max file size to 100 mb', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[1][0].maxSizeBytes).toEqual(
MAX_FILE_SIZE
);
});

it('sets securitySolution max file size to 100 mb', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[2][0].maxSizeBytes).toEqual(
MAX_FILE_SIZE
);
});
});

describe('custom file size', () => {
const config: FilesConfig = {
allowedMimeTypes: [],
maxSize: 5,
};

beforeEach(() => {
registerCaseFileKinds(config, mockFilesSetup);
});

it('sets cases max file size to 5', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[0][0].maxSizeBytes).toEqual(5);
});

it('sets observability max file size to 5', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[1][0].maxSizeBytes).toEqual(5);
});

it('sets securitySolution max file size to 5', () => {
expect(mockFilesSetup.registerFileKind.mock.calls[2][0].maxSizeBytes).toEqual(5);
});
});
});
});
});
29 changes: 17 additions & 12 deletions x-pack/plugins/cases/public/files/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,36 @@

import type { FilesSetup } from '@kbn/files-plugin/public';
import type { FileKindBrowser } from '@kbn/shared-ux-file-types';
import { ALLOWED_MIME_TYPES } from '../../common/constants/mime_types';
import { MAX_FILE_SIZE } from '../../common/constants';
import { MAX_FILE_SIZE, OWNERS } from '../../common/constants';
import type { Owner } from '../../common/constants/types';
import { APP_ID, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../common';
import { constructFileKindIdByOwner } from '../../common/files';
import type { CaseFileKinds, FilesConfig } from './types';

const buildFileKind = (owner: Owner): FileKindBrowser => {
const buildFileKind = (config: FilesConfig, owner: Owner): FileKindBrowser => {
return {
id: constructFileKindIdByOwner(owner),
allowedMimeTypes: ALLOWED_MIME_TYPES,
maxSizeBytes: MAX_FILE_SIZE,
allowedMimeTypes: config.allowedMimeTypes,
maxSizeBytes: config.maxSize ?? MAX_FILE_SIZE,
};
};

/**
* The file kind definition for interacting with the file service for the UI
*/
const CASES_FILE_KINDS: Record<Owner, FileKindBrowser> = {
[APP_ID]: buildFileKind(APP_ID),
[SECURITY_SOLUTION_OWNER]: buildFileKind(SECURITY_SOLUTION_OWNER),
[OBSERVABILITY_OWNER]: buildFileKind(OBSERVABILITY_OWNER),
const createFileKinds = (config: FilesConfig): CaseFileKinds => {
const caseFileKinds = new Map<Owner, FileKindBrowser>();

for (const owner of OWNERS) {
caseFileKinds.set(owner, buildFileKind(config, owner));
}

return caseFileKinds;
};

export const registerCaseFileKinds = (filesSetupPlugin: FilesSetup) => {
for (const fileKind of Object.values(CASES_FILE_KINDS)) {
export const registerCaseFileKinds = (config: FilesConfig, filesSetupPlugin: FilesSetup) => {
const fileKinds = createFileKinds(config);

for (const fileKind of fileKinds.values()) {
filesSetupPlugin.registerFileKind(fileKind);
}
};
14 changes: 14 additions & 0 deletions x-pack/plugins/cases/public/files/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { FileKindBrowser } from '@kbn/shared-ux-file-types';
import type { Owner } from '../../common/constants/types';
import type { CasesUiConfigType } from '../containers/types';

export type FilesConfig = CasesUiConfigType['files'];

export type CaseFileKinds = Map<Owner, FileKindBrowser>;
11 changes: 9 additions & 2 deletions x-pack/plugins/cases/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export class CasesUiPlugin
const externalReferenceAttachmentTypeRegistry = this.externalReferenceAttachmentTypeRegistry;
const persistableStateAttachmentTypeRegistry = this.persistableStateAttachmentTypeRegistry;

registerCaseFileKinds(plugins.files);
const config = this.initializerContext.config.get<CasesUiConfigType>();
registerCaseFileKinds(config.files, plugins.files);

if (plugins.home) {
plugins.home.featureCatalogue.register({
Expand Down Expand Up @@ -106,7 +107,13 @@ export class CasesUiPlugin

public start(core: CoreStart, plugins: CasesPluginStart): CasesUiStart {
const config = this.initializerContext.config.get<CasesUiConfigType>();
KibanaServices.init({ ...core, ...plugins, kibanaVersion: this.kibanaVersion, config });

KibanaServices.init({
...core,
...plugins,
kibanaVersion: this.kibanaVersion,
config,
});

/**
* getCasesContextLazy returns a new component each time is being called. To avoid re-renders
Expand Down
113 changes: 113 additions & 0 deletions x-pack/plugins/cases/server/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { ConfigSchema } from './config';

describe('config validation', () => {
describe('defaults', () => {
it('sets the defaults correctly', () => {
expect(ConfigSchema.validate({})).toMatchInlineSnapshot(`
Object {
"files": Object {
"allowedMimeTypes": Array [
"image/aces",
"image/apng",
"image/avci",
"image/avcs",
"image/avif",
"image/bmp",
"image/cgm",
"image/dicom-rle",
"image/dpx",
"image/emf",
"image/example",
"image/fits",
"image/g3fax",
"image/heic",
"image/heic-sequence",
"image/heif",
"image/heif-sequence",
"image/hej2k",
"image/hsj2",
"image/jls",
"image/jp2",
"image/jpeg",
"image/jph",
"image/jphc",
"image/jpm",
"image/jpx",
"image/jxr",
"image/jxrA",
"image/jxrS",
"image/jxs",
"image/jxsc",
"image/jxsi",
"image/jxss",
"image/ktx",
"image/ktx2",
"image/naplps",
"image/png",
"image/prs.btif",
"image/prs.pti",
"image/pwg-raster",
"image/svg+xml",
"image/t38",
"image/tiff",
"image/tiff-fx",
"image/vnd.adobe.photoshop",
"image/vnd.airzip.accelerator.azv",
"image/vnd.cns.inf2",
"image/vnd.dece.graphic",
"image/vnd.djvu",
"image/vnd.dwg",
"image/vnd.dxf",
"image/vnd.dvb.subtitle",
"image/vnd.fastbidsheet",
"image/vnd.fpx",
"image/vnd.fst",
"image/vnd.fujixerox.edmics-mmr",
"image/vnd.fujixerox.edmics-rlc",
"image/vnd.globalgraphics.pgb",
"image/vnd.microsoft.icon",
"image/vnd.mix",
"image/vnd.ms-modi",
"image/vnd.mozilla.apng",
"image/vnd.net-fpx",
"image/vnd.pco.b16",
"image/vnd.radiance",
"image/vnd.sealed.png",
"image/vnd.sealedmedia.softseal.gif",
"image/vnd.sealedmedia.softseal.jpg",
"image/vnd.svf",
"image/vnd.tencent.tap",
"image/vnd.valve.source.texture",
"image/vnd.wap.wbmp",
"image/vnd.xiff",
"image/vnd.zbrush.pcx",
"image/webp",
"image/wmf",
"text/plain",
"text/csv",
"text/json",
"application/json",
"application/zip",
"application/gzip",
"application/x-bzip",
"application/x-bzip2",
"application/x-7z-compressed",
"application/x-tar",
"application/pdf",
],
},
"markdownPlugins": Object {
"lens": true,
},
}
`);
});
});
});
Loading

0 comments on commit 234d48d

Please sign in to comment.