Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src-docs/src/views/markdown_editor/mardown_format_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
EuiMarkdownFormat,
EuiText,
EuiCode,
EuiCodeBlock,
} from '../../../../src/components';

import { Link } from 'react-router-dom';
Expand All @@ -16,6 +17,9 @@ const markdownFormatSource = require('!!raw-loader!./markdown_format');
import MarkdownFormatStyles from './markdown_format_styles';
const markdownFormatStylesSource = require('!!raw-loader!./markdown_format_styles');

import MarkdownFormatLinks, { markdownContent } from './markdown_format_links';
const markdownFormatLinksSource = require('!!raw-loader!./markdown_format_links');

import MarkdownFormatSink from './markdown_format_sink';
const markdownFormatSinkSource = require('!!raw-loader!./markdown_format_sink');

Expand Down Expand Up @@ -87,6 +91,33 @@ export const MarkdownFormatExample = {
},
demo: <MarkdownFormatStyles />,
},
{
source: [
{
type: GuideSectionTypes.JS,
code: markdownFormatLinksSource,
},
],
title: 'Link validation for security',
text: (
<>
<p>
Markdown content often comes from untrusted sources like user
generated content. To help with potential security issues,{' '}
<EuiCode>EuiMarkdownRenderer</EuiCode> only renders links if they
begin with <EuiCode>https:</EuiCode>, <EuiCode>https</EuiCode>, or{' '}
<EuiCode>/</EuiCode>. Links that do not meet these requirements are
rendered in the markdown link format, with the exception of{' '}
<EuiCode>mailto:</EuiCode> where only the link copy is kept.
</p>
<EuiCodeBlock language="markdown">{markdownContent}</EuiCodeBlock>
</>
),
props: {
EuiMarkdownFormat,
},
demo: <MarkdownFormatLinks />,
},
{
source: [
{
Expand Down
27 changes: 27 additions & 0 deletions src-docs/src/views/markdown_editor/markdown_format_links.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import { EuiMarkdownFormat } from '../../../../src';

// eslint-disable-next-line no-restricted-globals
const locationPathname = location.pathname;

export const markdownContent = `**Links starting with http:, https:, and / are valid:**

* https://elastic.com
* http://elastic.com
* https link to [elastic.co](https://elastic.co)
* http link to [elastic.co](http://elastic.co)
* relative link to [eui doc's homepage](${locationPathname})

**Other link protocols are kept as their markdown source:**
* ftp://elastic.co
* An [ftp link](ftp://elastic.co)

**mailto: renders with only the link copy**
* Send a note to [someone](mailto:[email protected])
* Also applies to inline email addresses like [email protected]
`;

export default () => {
return <EuiMarkdownFormat>{markdownContent}</EuiMarkdownFormat>;
};
34 changes: 34 additions & 0 deletions src-docs/src/views/markdown_editor/markdown_link_validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import {
getDefaultEuiMarkdownParsingPlugins,
euiMarkdownLinkValidator,
EuiMarkdownFormat,
} from '../../../../src/components';

// find the validation plugin and configure it to only allow https: and mailto: links
const parsingPlugins = getDefaultEuiMarkdownParsingPlugins();
parsingPlugins.find(([plugin, config]) => {
const isValidationPlugin = plugin === euiMarkdownLinkValidator;
if (isValidationPlugin) {
config.allowProtocols = ['https:', 'mailto:'];
}
return isValidationPlugin;
});

const markdown = `**Standalone links**
https://example.com
http://example.com
[email protected]

**As markdown syntax**
[example.com, https](https://example.com)
[example.com, http](http://example.com)
[email [email protected]](mailto:[email protected])
`;

export default () => (
<EuiMarkdownFormat parsingPluginList={parsingPlugins}>
{markdown}
</EuiMarkdownFormat>
);
183 changes: 183 additions & 0 deletions src-docs/src/views/markdown_editor/markdown_plugin_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { Link } from 'react-router-dom';
import MarkdownEditorWithPlugins from './markdown_editor_with_plugins';
const markdownEditorWithPluginsSource = require('!!raw-loader!./markdown_editor_with_plugins');

const linkValidationSource = require('!!raw-loader!./markdown_link_validation');
import LinkValidation from './markdown_link_validation';

const pluginSnippet = `<EuiMarkdownEditor
uiPlugin={myPluginUI}
parsingPluginList={myPluginParsingList}
Expand Down Expand Up @@ -170,6 +173,186 @@ export const MarkdownPluginExample = {
),

sections: [
{
title: 'Default plugins',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new documentation is fantastic!! Thank you for adding it!

text: (
<>
<p>
EUI provides additional plugins by default, but these can be omitted
or otherwise customized by providing the{' '}
<EuiCode>parsingPluginList</EuiCode>,{' '}
<EuiCode>processingPluginList</EuiCode>, and{' '}
<EuiCode>uiPlugins</EuiCode> props to the editor and formatter
components.
</p>
<p>The parsing plugins, responsible for parsing markdown are:</p>
<ol>
<li>
<EuiLink
href="https://www.npmjs.com/package/remark-parse"
external
>
remark-parse
</EuiLink>
</li>
<li>
<EuiLink
href="https://github.com/elastic/eui/blob/main/src/components/markdown_editor/plugins/remark/remark_prismjs.ts"
external
>
additional pre-processing for code blocks
</EuiLink>
</li>
<li>
<EuiLink
href="https://www.npmjs.com/package/remark-emoji"
external
>
remark-emoji
</EuiLink>
</li>
<li>
<EuiLink
href="https://www.npmjs.com/package/remark-breaks"
external
>
remark-breaks
</EuiLink>
</li>
<li>
<EuiLink
href="https://github.com/elastic/eui/blob/main/src/components/markdown_editor/plugins/markdown_link_validator.tsx"
external
>
link validation for security
</EuiLink>
</li>
<li>
<EuiLink
href="https://github.com/elastic/eui/blob/main/src/components/markdown_editor/plugins/markdown_checkbox/parser.ts"
external
>
injection of EuiCheckbox for markdown check boxes
</EuiLink>
</li>
<li>
<EuiLink
href="https://github.com/elastic/eui/blob/main/src/components/markdown_editor/plugins/markdown_tooltip/parser.ts"
external
>
tooltip plugin parser
</EuiLink>
</li>
</ol>
<p>
The above set provides an abstract syntax tree used by the editor to
provide feedback, and the renderer passes that output to the set of
processing plugins to allow it to be rendered:
</p>
<ol>
<li>
<EuiLink
href="https://www.npmjs.com/package/remark-rehype"
external
>
remark-rehype
</EuiLink>
</li>
<li>
<EuiLink
href="https://www.npmjs.com/package/rehype-react"
external
>
rehype-react
</EuiLink>
</li>
<li>
<EuiLink
href="https://github.com/elastic/eui/blob/main/src/components/markdown_editor/plugins/markdown_tooltip/renderer.tsx"
external
>
tooltip plugin renderer
</EuiLink>
</li>
</ol>
<p>
The last set of plugin configuration - <EuiCode>uiPlugins</EuiCode>{' '}
- allows toolbar buttons to be defined and how they alter or inject
markdown and returns with only one plugin:
</p>
<ol>
<li>
<EuiLink
href="https://github.com/elastic/eui/blob/main/src/components/markdown_editor/plugins/markdown_tooltip/plugin.tsx"
external
>
tooltip plugin ui
</EuiLink>
</li>
</ol>
<p>
These plugin definitions can be obtained by calling{' '}
<EuiCode>getDefaultEuiMarkdownParsingPlugins</EuiCode>,{' '}
<EuiCode>getDefaultEuiMarkdownProcessingPlugins</EuiCode>, and{' '}
<EuiCode>getDefaultEuiMarkdownUiPlugins</EuiCode> respectively. Each
of these three functions take an optional configuration object with
an <EuiCode>exclude</EuiCode> key, an array of EUI-defaulted plugins
to disable. Currently the only option this configuration can take is{' '}
<EuiCode>&apos;tooltip&apos;</EuiCode>.
</p>
</>
),
},
{
source: [
{
type: GuideSectionTypes.JS,
code: linkValidationSource,
},
],
title: 'Link validation & security',
text: (
<Fragment>
<p>
To enhance user and application security, the default behavior
removes links to URLs that aren&apos;t relative (beginning with{' '}
<EuiCode>/</EuiCode>) and don&apos;t use the{' '}
<EuiCode>https:</EuiCode>, <EuiCode>http:</EuiCode>, or{' '}
<EuiCode>mailto:</EuiCode> protocols. This validation can be further
configured or removed altogether.
</p>
<p>
In this example only <EuiCode>https:</EuiCode> and{' '}
<EuiCode>mailto:</EuiCode> links are allowed.
</p>
</Fragment>
),
snippet: [
`// change what link protocols are allowed
const parsingPlugins = getDefaultEuiMarkdownParsingPlugins();
parsingPlugins.find(([plugin, config]) => {
const isValidationPlugin = plugin === euiMarkdownLinkValidator;
if (isValidationPlugin) {
config.allowProtocols = ['https:', 'mailto:'];
}
return isValidationPlugin;
});`,
`// filter out the link validation plugin
const parsingPlugins = getDefaultEuiMarkdownParsingPlugins().filter(([plugin]) => {
return plugin !== euiMarkdownLinkValidator;
});`,
`// disable relative urls
const parsingPlugins = getDefaultEuiMarkdownParsingPlugins();
parsingPlugins.find(([plugin, config]) => {
const isValidationPlugin = plugin === euiMarkdownLinkValidator;
if (isValidationPlugin) {
config.allowRelative = false;
}
return isValidationPlugin;
});`,
],
demo: <LinkValidation />,
},
{
wrapText: false,
text: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ exports[`EuiMarkdownEditor custom plugins are excluded and popover is rendered 1
],
Array [
[Function],
Object {},
Object {
"allowProtocols": Array [
"https:",
"http:",
"mailto:",
],
"allowRelative": true,
},
],
Array [
[Function],
Expand Down
2 changes: 2 additions & 0 deletions src/components/markdown_editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export type {
RemarkRehypeHandler,
RemarkTokenizer,
} from './markdown_types';
export { euiMarkdownLinkValidator } from './plugins/markdown_link_validator';
export type { EuiMarkdownLinkValidatorOptions } from './plugins/markdown_link_validator';
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import breaks from 'remark-breaks';
import highlight from '../remark/remark_prismjs';
import * as MarkdownTooltip from '../markdown_tooltip';
import * as MarkdownCheckbox from '../markdown_checkbox';
import { markdownLinkValidator } from '../markdown_link_validator';
import {
euiMarkdownLinkValidator,
EuiMarkdownLinkValidatorOptions,
} from '../markdown_link_validator';

export type DefaultEuiMarkdownParsingPlugins = PluggableList;

Expand All @@ -41,7 +44,13 @@ export const getDefaultEuiMarkdownParsingPlugins = ({
[highlight, {}],
[emoji, { emoticon: false }],
[breaks, {}],
[markdownLinkValidator, {}],
[
euiMarkdownLinkValidator,
{
allowRelative: true,
allowProtocols: ['https:', 'http:', 'mailto:'],
} as EuiMarkdownLinkValidatorOptions,
],
[MarkdownCheckbox.parser, {}],
];

Expand Down
Loading