[cr] whitelist flexbox styles in text panel editor (#43222)

* [cr] whitelist flexbox styles in text panel editor

* [cr] separate sanitize function for text panel only

* [cr] separate markdown function for text panel

* [cr] common markdown options
This commit is contained in:
Coleman Rollins 2021-12-21 10:32:48 -06:00 committed by GitHub
parent 6abced840d
commit 119f756c0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 13 deletions

View File

@ -1,11 +1,12 @@
export * from './string';
export * from './markdown';
export * from './text';
import { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl } from './sanitize';
import { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl, sanitizeTextPanelContent } from './sanitize';
export const textUtil = {
escapeHtml,
hasAnsiCodes,
sanitize,
sanitizeTextPanelContent,
sanitizeUrl,
};

View File

@ -1,4 +1,5 @@
import { renderMarkdown } from './markdown';
import { sanitizeTextPanelContent } from './sanitize';
describe('Markdown wrapper', () => {
it('should be able to handle undefined value', () => {
@ -10,4 +11,13 @@ describe('Markdown wrapper', () => {
const str = renderMarkdown('<script>alert()</script>');
expect(str).toBe('&lt;script&gt;alert()&lt;/script&gt;');
});
it('should allow whitelisted styles in text panel', () => {
const html =
'<div style="display:flex; flex-direction: column; flex-wrap: wrap; justify-content: start; gap: 2px;"><div style="flex-basis: 50%"></div></div>';
const str = sanitizeTextPanelContent(html);
expect(str).toBe(
'<div style="display:flex; flex-direction:column; flex-wrap:wrap; justify-content:start; gap:2px;"><div style="flex-basis:50%;"></div></div>'
);
});
});

View File

@ -1,5 +1,5 @@
import { marked } from 'marked';
import { sanitize } from './sanitize';
import { sanitize, sanitizeTextPanelContent } from './sanitize';
let hasInitialized = false;
@ -7,15 +7,17 @@ export interface RenderMarkdownOptions {
noSanitize?: boolean;
}
const markdownOptions = {
pedantic: false,
gfm: true,
smartLists: true,
smartypants: false,
xhtml: false,
};
export function renderMarkdown(str?: string, options?: RenderMarkdownOptions): string {
if (!hasInitialized) {
marked.setOptions({
pedantic: false,
gfm: true,
smartLists: true,
smartypants: false,
xhtml: false,
});
marked.setOptions({ ...markdownOptions });
hasInitialized = true;
}
@ -26,3 +28,17 @@ export function renderMarkdown(str?: string, options?: RenderMarkdownOptions): s
return sanitize(html);
}
export function renderTextPanelMarkdown(str?: string, options?: RenderMarkdownOptions): string {
if (!hasInitialized) {
marked.setOptions({ ...markdownOptions });
hasInitialized = true;
}
const html = marked(str || '');
if (options?.noSanitize) {
return html;
}
return sanitizeTextPanelContent(html);
}

View File

@ -10,6 +10,29 @@ const sanitizeXSS = new FilterXSS({
whiteList: XSSWL,
});
const sanitizeTextPanelWhitelist = new xss.FilterXSS({
whiteList: XSSWL,
css: {
whiteList: {
...xss.getDefaultCSSWhiteList(),
'flex-direction': true,
'flex-wrap': true,
'flex-basis': true,
'flex-grow': true,
'flex-shrink': true,
'flex-flow': true,
gap: true,
order: true,
'justify-content': true,
'justify-items': true,
'justify-self': true,
'align-items': true,
'align-content': true,
'align-self': true,
},
},
});
/**
* Returns string safe from XSS attacks.
*
@ -26,6 +49,15 @@ export function sanitize(unsanitizedString: string): string {
}
}
export function sanitizeTextPanelContent(unsanitizedString: string): string {
try {
return sanitizeTextPanelWhitelist.process(unsanitizedString);
} catch (error) {
console.error('String could not be sanitized', unsanitizedString);
return 'Text string could not be sanitized';
}
}
export function sanitizeUrl(url: string): string {
return braintreeSanitizeUrl(url);
}

View File

@ -1,7 +1,7 @@
// Libraries
import React, { PureComponent } from 'react';
import { debounce } from 'lodash';
import { PanelProps, renderMarkdown, textUtil } from '@grafana/data';
import { PanelProps, renderTextPanelMarkdown, textUtil } from '@grafana/data';
// Utils
import config from 'app/core/config';
// Types
@ -44,7 +44,9 @@ export class TextPanel extends PureComponent<Props, State> {
prepareMarkdown(content: string): string {
// Sanitize is disabled here as we handle that after variable interpolation
return renderMarkdown(this.interpolateAndSanitizeString(content), { noSanitize: config.disableSanitizeHtml });
return renderTextPanelMarkdown(this.interpolateAndSanitizeString(content), {
noSanitize: config.disableSanitizeHtml,
});
}
interpolateAndSanitizeString(content: string): string {
@ -52,7 +54,7 @@ export class TextPanel extends PureComponent<Props, State> {
content = replaceVariables(content, {}, 'html');
return config.disableSanitizeHtml ? content : textUtil.sanitize(content);
return config.disableSanitizeHtml ? content : textUtil.sanitizeTextPanelContent(content);
}
processContent(options: PanelOptions): string {

View File

@ -40,7 +40,7 @@ export const TextPanelEditor: FC<StandardEditorProps<string, any, PanelOptions>>
width={width}
showMiniMap={false}
showLineNumbers={false}
height="200px"
height="500px"
getSuggestions={getSuggestions}
/>
);