-
Notifications
You must be signed in to change notification settings - Fork 919
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Branding] Update resource validation procedure. #1019
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add examples for a PNG and GIF? Also an example of text and/or JavaScript failing would be ideal. |
||
const result = await service.isResourceValid( | ||
'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNjQgNjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik02MS43Mzc0IDIzLjVDNjAuNDg3OCAyMy41IDU5LjQ3NDggMjQuNTEzIDU5LjQ3NDggMjUuNzYyNkM1OS40NzQ4IDQ0LjM4MTMgNDQuMzgxMyA1OS40NzQ4IDI1Ljc2MjYgNTkuNDc0OEMyNC41MTMgNTkuNDc0OCAyMy41IDYwLjQ4NzggMjMuNSA2MS43Mzc0QzIzLjUgNjIuOTg3IDI0LjUxMyA2NCAyNS43NjI2IDY0QzQ2Ljg4MDUgNjQgNjQgNDYuODgwNSA2NCAyNS43NjI2QzY0IDI0LjUxMyA2Mi45ODcgMjMuNSA2MS43Mzc0IDIzLjVaIiBmaWxsPSIjMDA1RUI4Ii8+CjxwYXRoIGQ9Ik00OC4wODE0IDM4QzUwLjI1NzIgMzQuNDUwNSA1Mi4zNjE1IDI5LjcxNzggNTEuOTQ3NSAyMy4wOTIxQzUxLjA4OTkgOS4zNjcyNSAzOC42NTg5IC0xLjA0NDYzIDI2LjkyMDYgMC4wODM3MzI3QzIyLjMyNTMgMC41MjU0NjUgMTcuNjA2OCA0LjI3MTIgMTguMDI2IDEwLjk4MDVDMTguMjA4MiAxMy44OTYxIDE5LjYzNTIgMTUuNjE2OSAyMS45NTQ0IDE2LjkzOTlDMjQuMTYxOCAxOC4xOTkyIDI2Ljk5NzggMTguOTk2OSAzMC4yMTI4IDE5LjkwMTFDMzQuMDk2MiAyMC45OTM0IDM4LjYwMDkgMjIuMjIwMyA0Mi4wNjMgMjQuNzcxN0M0Ni4yMTI1IDI3LjgyOTUgNDkuMDQ5MSAzMS4zNzQzIDQ4LjA4MTQgMzhaIiBmaWxsPSIjMDAzQjVDIi8+CjxwYXRoIGQ9Ik0zLjkxODYxIDE0QzEuNzQyNzYgMTcuNTQ5NSAtMC4zNjE1MDYgMjIuMjgyMiAwLjA1MjQ5MzEgMjguOTA3OUMwLjkxMDA3MiA0Mi42MzI3IDEzLjM0MTEgNTMuMDQ0NiAyNS4wNzk0IDUxLjkxNjNDMjkuNjc0NyA1MS40NzQ1IDM0LjM5MzIgNDcuNzI4OCAzMy45NzQgNDEuMDE5NUMzMy43OTE4IDM4LjEwMzkgMzIuMzY0NyAzNi4zODMxIDMwLjA0NTYgMzUuMDYwMUMyNy44MzgyIDMzLjgwMDggMjUuMDAyMiAzMy4wMDMxIDIxLjc4NzIgMzIuMDk4OUMxNy45MDM4IDMxLjAwNjYgMTMuMzk5MSAyOS43Nzk3IDkuOTM2OTQgMjcuMjI4M0M1Ljc4NzQ2IDI0LjE3MDQgMi45NTA5MiAyMC42MjU3IDMuOTE4NjEgMTRaIiBmaWxsPSIjMDA1RUI4Ii8+Cjwvc3ZnPgo=', | ||
'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( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see SVG in this list, and the GIF signature is different. |
||
*/ | ||
const MIME_MAGIC = ['<svg', 'GIF8', '‰PNG']; | ||
|
||
/** @internal */ | ||
export class RenderingService { | ||
constructor(private readonly coreContext: CoreContext) {} | ||
|
@@ -261,28 +267,28 @@ export class RenderingService { | |
opensearchDashboardsConfig: Readonly<OpenSearchDashboardsConfigType> | ||
): Promise<BrandingValidation> => { | ||
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<string, any>; | ||
} | ||
|
||
/** | ||
* 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<boolean> => { | ||
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. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add functional tests to accompany the existing custom branding functional tests?