[Reporting/UI Settings] Validation for the Reporting UI Setting Custom Logo (#94746)
* Validation for the Reporting UI Setting Custom Logo * add more validations * check if image is too large * fix i18n * clean redundant * large test strings not necessary Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
37ff43b0c3
commit
0d1a1af57d
|
@ -5,10 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { PluginConfigDescriptor } from 'kibana/server';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import { ConfigSchema, ReportingConfigType } from './schema';
|
||||
export { buildConfig } from './config';
|
||||
export { registerUiSettings } from './ui_settings';
|
||||
export { ConfigSchema, ReportingConfigType };
|
||||
|
||||
export const config: PluginConfigDescriptor<ReportingConfigType> = {
|
||||
|
|
71
x-pack/plugins/reporting/server/config/ui_settings.test.ts
Normal file
71
x-pack/plugins/reporting/server/config/ui_settings.test.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 { range } from 'lodash';
|
||||
import { PdfLogoSchema } from './ui_settings';
|
||||
|
||||
test('validates when provided with image data', () => {
|
||||
const jpgString =
|
||||
`data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBcUFBUUExUYGRUaGRsZGxsZHB8bIh0iGhgbGxkbGx8dIy0kGx0rIiIbJTcoKi8xNDU0ISY6Pzo2` +
|
||||
`+8snFz9eWgvYKS4ZsvS05zRQsDveIzH4Er4iDtr6iICIiAiIgIiICIiD//2Q==`;
|
||||
expect(PdfLogoSchema.validate(jpgString)).toBe(jpgString);
|
||||
|
||||
const pngString =
|
||||
`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAO4AAADUCAMAAACs0e/bAAAAjVBMVEX////8/Pz4+Pj5+fnb29vz8/Px8fFeXl7r6+u/v79nZ` +
|
||||
`tcAAAAASUVORK5CYII=`;
|
||||
expect(PdfLogoSchema.validate(pngString)).toBe(pngString);
|
||||
|
||||
const gifString =
|
||||
`data:image/gif;base64,R0lGODlhoADIAPYAAO/w7wgFBwsLCxMTExsbGyMjI5SUlLS0tLu7u9vb2+Hh4e/v7/Ds7////0NDQ2RkZCkXJO/w8PLy8g8QD` +
|
||||
`53IIefTH3WR4N8lXzvKWu/zlMI+5zGdO85rb/OY4z7nOd87znvv850APutCHTvSiG/3oSE+60pfO9KY7/elQj7rU5xIIADs=`;
|
||||
expect(PdfLogoSchema.validate(gifString)).toBe(gifString);
|
||||
|
||||
const svgString =
|
||||
`data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXR` +
|
||||
`AgPC9nPgogIDwvZz4KPC9zdmc+Cg==`;
|
||||
expect(PdfLogoSchema.validate(svgString)).toBe(svgString);
|
||||
});
|
||||
|
||||
test('validates if provided with null / undefined value', () => {
|
||||
expect(() => PdfLogoSchema.validate(undefined)).not.toThrow();
|
||||
expect(() => PdfLogoSchema.validate(null)).not.toThrow();
|
||||
});
|
||||
|
||||
test('throws validation error if provided with data over max size', () => {
|
||||
const largeJpgMock =
|
||||
`data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBcUFBUUExUYGRUaGRsZGxsZHB8bIh0iGhgbGxkbGx8dIy0kGx0rIiIbJTcoKi8xNDU0ISY6Pzo2` +
|
||||
range(0, 2050)
|
||||
.map(
|
||||
() =>
|
||||
`Pi0zNDMBCwsLBgYGEAYGEDEcFRwxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMf/AABEIAOgA2gMBIgACEQEDEQH/xAAcAAEAAgMBAQE`
|
||||
)
|
||||
.join('') +
|
||||
`+8snFz9eWgvYKS4ZsvS05zRQsDveIzH4Er4iDtr6iICIiAiIgIiICIiD//2Q==`;
|
||||
expect(() => PdfLogoSchema.validate(largeJpgMock)).toThrowError(/too large/);
|
||||
});
|
||||
|
||||
test('throws validation error if provided with non-image data', () => {
|
||||
const invalidErrorMatcher = /try a different image/;
|
||||
|
||||
expect(() => PdfLogoSchema.validate('')).toThrowError(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(true)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(false)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate({})).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate([])).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(0)).toThrow(invalidErrorMatcher);
|
||||
expect(() => PdfLogoSchema.validate(0x00f)).toThrow(invalidErrorMatcher);
|
||||
|
||||
const csvString =
|
||||
`data:text/csv;base64,Il9pZCIsIl9pbmRleCIsIl9zY29yZSIsIl90eXBlIiwiZm9vLmJhciIsImZvby5iYXIua2V5d29yZCIKZjY1QU9IZ0J5bFZmWW04W` +
|
||||
`TRvb1EsYmVlLDEsIi0iLGJheixiYXoKbks1QU9IZ0J5bFZmWW04WTdZcUcsYmVlLDEsIi0iLGJvbyxib28K`;
|
||||
expect(() => PdfLogoSchema.validate(csvString)).toThrow(invalidErrorMatcher);
|
||||
|
||||
const scriptString =
|
||||
`data:application/octet-stream;base64,QEVDSE8gT0ZGCldFRUtPRllSLkNPTSB8IEZJTkQgIlRoaXMgaXMiID4gVEVNUC5CQV` +
|
||||
`QKRUNITz5USElTLkJBVCBTRVQgV0VFSz0lJTMKQ0FMTCBURU1QLkJBVApERUwgIFRFTVAuQkFUCkRFTCAgVEhJUy5CQVQKRUNITyBXZWVrICVXRUVLJQo=`;
|
||||
expect(() => PdfLogoSchema.validate(scriptString)).toThrow(invalidErrorMatcher);
|
||||
});
|
81
x-pack/plugins/reporting/server/config/ui_settings.ts
Normal file
81
x-pack/plugins/reporting/server/config/ui_settings.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreSetup, UiSettingsParams } from 'kibana/server';
|
||||
import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../common/constants';
|
||||
|
||||
const kbToBase64Length = (kb: number) => Math.floor((kb * 1024 * 8) / 6);
|
||||
const maxLogoSizeInKilobytes = kbToBase64Length(200);
|
||||
|
||||
// inspired by x-pack/plugins/canvas/common/lib/dataurl.ts
|
||||
const dataurlRegex = /^data:([a-z]+\/[a-z0-9-+.]+)(;[a-z-]+=[a-z0-9-]+)?(;([a-z0-9]+))?,/;
|
||||
const imageTypes = ['image/svg+xml', 'image/jpeg', 'image/png', 'image/gif'];
|
||||
|
||||
const isImageData = (str: any): boolean => {
|
||||
const matches = str.match(dataurlRegex);
|
||||
|
||||
if (!matches) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [, mimetype, , , encoding] = matches;
|
||||
const imageTypeIndex = imageTypes.indexOf(mimetype);
|
||||
if (imageTypeIndex < 0 || encoding !== 'base64') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const isLessThanMaxSize = (str: any) => {
|
||||
if (str.length > maxLogoSizeInKilobytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const validatePdfLogoBase64String = (str: any) => {
|
||||
if (typeof str !== 'string' || !isImageData(str)) {
|
||||
return i18n.translate('xpack.reporting.uiSettings.validate.customLogo.badFile', {
|
||||
defaultMessage: `Sorry, that file will not work. Please try a different image file.`,
|
||||
});
|
||||
}
|
||||
if (!isLessThanMaxSize(str)) {
|
||||
return i18n.translate('xpack.reporting.uiSettings.validate.customLogo.tooLarge', {
|
||||
defaultMessage: `Sorry, that file is too large. The image file must be less than 200 kilobytes.`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const PdfLogoSchema = schema.nullable(schema.any({ validate: validatePdfLogoBase64String }));
|
||||
|
||||
export function registerUiSettings(core: CoreSetup<object, unknown>) {
|
||||
core.uiSettings.register({
|
||||
[UI_SETTINGS_CUSTOM_PDF_LOGO]: {
|
||||
name: i18n.translate('xpack.reporting.pdfFooterImageLabel', {
|
||||
defaultMessage: 'PDF footer image',
|
||||
}),
|
||||
value: null,
|
||||
description: i18n.translate('xpack.reporting.pdfFooterImageDescription', {
|
||||
defaultMessage: `Custom image to use in the PDF's footer`,
|
||||
}),
|
||||
sensitive: true,
|
||||
type: 'image',
|
||||
schema: PdfLogoSchema,
|
||||
category: [PLUGIN_ID],
|
||||
validation: {
|
||||
maxSize: {
|
||||
length: maxLogoSizeInKilobytes,
|
||||
description: '200 kB',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Record<string, UiSettingsParams<null>>);
|
||||
}
|
|
@ -5,21 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server';
|
||||
import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from '../common/constants';
|
||||
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server';
|
||||
import { PLUGIN_ID } from '../common/constants';
|
||||
import { ReportingCore } from './';
|
||||
import { initializeBrowserDriverFactory } from './browsers';
|
||||
import { buildConfig, ReportingConfigType } from './config';
|
||||
import { buildConfig, registerUiSettings, ReportingConfigType } from './config';
|
||||
import { LevelLogger, ReportingStore } from './lib';
|
||||
import { registerRoutes } from './routes';
|
||||
import { setFieldFormats } from './services';
|
||||
import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types';
|
||||
import type {
|
||||
ReportingRequestHandlerContext,
|
||||
ReportingSetup,
|
||||
ReportingSetupDeps,
|
||||
ReportingStart,
|
||||
ReportingStartDeps,
|
||||
} from './types';
|
||||
import { registerReportingUsageCollector } from './usage';
|
||||
import type { ReportingRequestHandlerContext } from './types';
|
||||
|
||||
const kbToBase64Length = (kb: number) => Math.floor((kb * 1024 * 8) / 6);
|
||||
|
||||
export class ReportingPlugin
|
||||
implements Plugin<ReportingSetup, ReportingStart, ReportingSetupDeps, ReportingStartDeps> {
|
||||
|
@ -44,28 +45,7 @@ export class ReportingPlugin
|
|||
}
|
||||
});
|
||||
|
||||
core.uiSettings.register({
|
||||
[UI_SETTINGS_CUSTOM_PDF_LOGO]: {
|
||||
name: i18n.translate('xpack.reporting.pdfFooterImageLabel', {
|
||||
defaultMessage: 'PDF footer image',
|
||||
}),
|
||||
value: null,
|
||||
description: i18n.translate('xpack.reporting.pdfFooterImageDescription', {
|
||||
defaultMessage: `Custom image to use in the PDF's footer`,
|
||||
}),
|
||||
sensitive: true,
|
||||
type: 'image',
|
||||
schema: schema.nullable(schema.byteSize({ max: '200kb' })),
|
||||
category: [PLUGIN_ID],
|
||||
// Used client-side for size validation
|
||||
validation: {
|
||||
maxSize: {
|
||||
length: kbToBase64Length(200),
|
||||
description: '200 kB',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
registerUiSettings(core);
|
||||
|
||||
const { elasticsearch, http } = core;
|
||||
const { features, licensing, security, spaces, taskManager } = plugins;
|
||||
|
|
Loading…
Reference in a new issue