From 501a82ac6b298392e6d2b0acb040625111e01ee3 Mon Sep 17 00:00:00 2001 From: Serge Arbuzov Date: Fri, 4 Feb 2022 16:08:13 +0000 Subject: [PATCH 1/2] Update resource validation procedure. New validation procedure allows to embed base64 encoded images Signed-off-by: Serge Arbuzov --- .../rendering/rendering_service.test.ts | 23 +++++++++++ .../server/rendering/rendering_service.tsx | 39 +++++++++++++++---- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index e4d52dc13648..56dd1b30ec3b 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -137,6 +137,29 @@ describe('RenderingService', () => { }); }); + describe('isResourceValid()', () => { + it('checks valid URL', async () => { + const result = await service.isResourceValid( + 'https://opensearch.org/assets/brand/SVG/Mark/opensearch_mark_default.svg', + 'config' + ); + expect(result).toEqual(true); + }); + + it('checks valid SVG', async () => { + const result = await service.isResourceValid( + 'data:image/svg+xml;base64,CjxpbWcgc3R5bGU9IndpZHRoOiAxMDAlOyBoZWlnaHQ6IGF1dG87IGZsb2F0OiBsZWZ0O2JhY2tncm91bmQtaW1hZ2U6IG5vbmU7IiBzcmM9Ii8vcGljLm9ubGluZXdlYmZvbnRzLmNvbS9zdmcvaW1nXzIzOTMwOC5wbmciIGFsdD0iU2VsZWN0IFNtYWxsIiB3aWR0aD0iNTAwIiBoZWlnaHQ9IjUwMCI+CiAg', + 'config' + ); + expect(result).toEqual(true); + }); + + it('checks invalid resource', async () => { + const result = await service.isResourceValid('some garbage', 'config'); + expect(result).toEqual(false); + }); + }); + describe('isUrlValid()', () => { it('checks valid SVG URL', async () => { const result = await service.isUrlValid( diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 5185ef6a6154..77bb09b6f420 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -53,6 +53,12 @@ import { OpenSearchDashboardsConfigType } from '../opensearch_dashboards_config' const DEFAULT_TITLE = 'OpenSearch Dashboards'; +/** + * Magic constants for the specific mime types + * @see https://en.wikipedia.org/wiki/List_of_file_signatures + */ +const MIME_MAGIC = [' ): Promise => { const branding = opensearchDashboardsConfig.branding; - const isLogoDefaultValid = await this.isUrlValid(branding.logo.defaultUrl, 'logo default'); + const isLogoDefaultValid = await this.isResourceValid(branding.logo.defaultUrl, 'logo default'); const isLogoDarkmodeValid = darkMode - ? await this.isUrlValid(branding.logo.darkModeUrl, 'logo darkMode') + ? await this.isResourceValid(branding.logo.darkModeUrl, 'logo darkMode') : false; - const isMarkDefaultValid = await this.isUrlValid(branding.mark.defaultUrl, 'mark default'); + const isMarkDefaultValid = await this.isResourceValid(branding.mark.defaultUrl, 'mark default'); const isMarkDarkmodeValid = darkMode - ? await this.isUrlValid(branding.mark.darkModeUrl, 'mark darkMode') + ? await this.isResourceValid(branding.mark.darkModeUrl, 'mark darkMode') : false; - const isLoadingLogoDefaultValid = await this.isUrlValid( + const isLoadingLogoDefaultValid = await this.isResourceValid( branding.loadingLogo.defaultUrl, 'loadingLogo default' ); const isLoadingLogoDarkmodeValid = darkMode - ? await this.isUrlValid(branding.loadingLogo.darkModeUrl, 'loadingLogo darkMode') + ? await this.isResourceValid(branding.loadingLogo.darkModeUrl, 'loadingLogo darkMode') : false; - const isFaviconValid = await this.isUrlValid(branding.faviconUrl, 'favicon'); + const isFaviconValid = await this.isResourceValid(branding.faviconUrl, 'favicon'); const isTitleValid = this.isTitleValid(branding.applicationTitle, 'applicationTitle'); @@ -306,6 +312,25 @@ export class RenderingService { return ((await browserConfig?.pipe(take(1)).toPromise()) ?? {}) as Record; } + /** + * Validation function for resources provided for the branding. + * Checks if it`s a base64 encoded resource is it`s not check it as URL + * + * @param {string} resource + * @param {string} configName + * @returns {boolean} indicate if the provided resource can be used in the system + */ + public isResourceValid = async (resource: string, configName?: string): Promise => { + if (resource.trim().startsWith('data:image')) { + const buff = Buffer.from(resource.replace(/^data:image\/.*;base64,/, ''), 'base64'); + const extractedBytes = buff.subarray(0, 4).toString(); + if (MIME_MAGIC.includes(extractedBytes)) { + return true; + } + } + return this.isUrlValid(resource, configName); + }; + /** * Validation function for URLs. Use Axios to call URL and check validity. * Also needs to be ended with png, svg, gif, PNG, SVG and GIF. From 9bdae6a9d5656385e003e471ac77637507626f45 Mon Sep 17 00:00:00 2001 From: Kawika Avilla Date: Mon, 28 Feb 2022 08:14:50 +0000 Subject: [PATCH 2/2] [Branding] Update failing jest unit test Signed-off-by: Kawika Avilla --- src/core/server/rendering/__mocks__/rendering_service.ts | 2 ++ src/core/server/rendering/rendering_service.test.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts index 98576eea07cd..3fe98c548297 100644 --- a/src/core/server/rendering/__mocks__/rendering_service.ts +++ b/src/core/server/rendering/__mocks__/rendering_service.ts @@ -41,11 +41,13 @@ export const setupMock: jest.Mocked = { }; export const mockSetup = jest.fn().mockResolvedValue(setupMock); export const mockStop = jest.fn(); +export const mockIsResourceValid = jest.fn(); export const mockIsUrlValid = jest.fn(); export const mockIsTitleValid = jest.fn(); export const mockRenderingService: jest.Mocked = { setup: mockSetup, stop: mockStop, + isResourceValid: mockIsResourceValid, isUrlValid: mockIsUrlValid, isTitleValid: mockIsTitleValid, }; diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 56dd1b30ec3b..6f59fd8a004d 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -148,7 +148,7 @@ describe('RenderingService', () => { it('checks valid SVG', async () => { const result = await service.isResourceValid( - 'data:image/svg+xml;base64,CjxpbWcgc3R5bGU9IndpZHRoOiAxMDAlOyBoZWlnaHQ6IGF1dG87IGZsb2F0OiBsZWZ0O2JhY2tncm91bmQtaW1hZ2U6IG5vbmU7IiBzcmM9Ii8vcGljLm9ubGluZXdlYmZvbnRzLmNvbS9zdmcvaW1nXzIzOTMwOC5wbmciIGFsdD0iU2VsZWN0IFNtYWxsIiB3aWR0aD0iNTAwIiBoZWlnaHQ9IjUwMCI+CiAg', + 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNjQgNjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik02MS43Mzc0IDIzLjVDNjAuNDg3OCAyMy41IDU5LjQ3NDggMjQuNTEzIDU5LjQ3NDggMjUuNzYyNkM1OS40NzQ4IDQ0LjM4MTMgNDQuMzgxMyA1OS40NzQ4IDI1Ljc2MjYgNTkuNDc0OEMyNC41MTMgNTkuNDc0OCAyMy41IDYwLjQ4NzggMjMuNSA2MS43Mzc0QzIzLjUgNjIuOTg3IDI0LjUxMyA2NCAyNS43NjI2IDY0QzQ2Ljg4MDUgNjQgNjQgNDYuODgwNSA2NCAyNS43NjI2QzY0IDI0LjUxMyA2Mi45ODcgMjMuNSA2MS43Mzc0IDIzLjVaIiBmaWxsPSIjMDA1RUI4Ii8+CjxwYXRoIGQ9Ik00OC4wODE0IDM4QzUwLjI1NzIgMzQuNDUwNSA1Mi4zNjE1IDI5LjcxNzggNTEuOTQ3NSAyMy4wOTIxQzUxLjA4OTkgOS4zNjcyNSAzOC42NTg5IC0xLjA0NDYzIDI2LjkyMDYgMC4wODM3MzI3QzIyLjMyNTMgMC41MjU0NjUgMTcuNjA2OCA0LjI3MTIgMTguMDI2IDEwLjk4MDVDMTguMjA4MiAxMy44OTYxIDE5LjYzNTIgMTUuNjE2OSAyMS45NTQ0IDE2LjkzOTlDMjQuMTYxOCAxOC4xOTkyIDI2Ljk5NzggMTguOTk2OSAzMC4yMTI4IDE5LjkwMTFDMzQuMDk2MiAyMC45OTM0IDM4LjYwMDkgMjIuMjIwMyA0Mi4wNjMgMjQuNzcxN0M0Ni4yMTI1IDI3LjgyOTUgNDkuMDQ5MSAzMS4zNzQzIDQ4LjA4MTQgMzhaIiBmaWxsPSIjMDAzQjVDIi8+CjxwYXRoIGQ9Ik0zLjkxODYxIDE0QzEuNzQyNzYgMTcuNTQ5NSAtMC4zNjE1MDYgMjIuMjgyMiAwLjA1MjQ5MzEgMjguOTA3OUMwLjkxMDA3MiA0Mi42MzI3IDEzLjM0MTEgNTMuMDQ0NiAyNS4wNzk0IDUxLjkxNjNDMjkuNjc0NyA1MS40NzQ1IDM0LjM5MzIgNDcuNzI4OCAzMy45NzQgNDEuMDE5NUMzMy43OTE4IDM4LjEwMzkgMzIuMzY0NyAzNi4zODMxIDMwLjA0NTYgMzUuMDYwMUMyNy44MzgyIDMzLjgwMDggMjUuMDAyMiAzMy4wMDMxIDIxLjc4NzIgMzIuMDk4OUMxNy45MDM4IDMxLjAwNjYgMTMuMzk5MSAyOS43Nzk3IDkuOTM2OTQgMjcuMjI4M0M1Ljc4NzQ2IDI0LjE3MDQgMi45NTA5MiAyMC42MjU3IDMuOTE4NjEgMTRaIiBmaWxsPSIjMDA1RUI4Ii8+Cjwvc3ZnPgo=', 'config' ); expect(result).toEqual(true);