Skip to content

Commit

Permalink
refactor(core): provide wrapper for dangerously setting html (spinnak…
Browse files Browse the repository at this point in the history
  • Loading branch information
anotherchrisberry authored and mergify[bot] committed Jan 6, 2020
1 parent 1bb3a27 commit 6548872
Show file tree
Hide file tree
Showing 13 changed files with 62 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import DOMPurify from 'dompurify';
import { Field, FormikProps } from 'formik';

import {
Expand All @@ -12,6 +11,7 @@ import {
ReactInjector,
IServerGroup,
IWizardPageComponent,
Markdown,
TaskReason,
} from '@spinnaker/core';

Expand Down Expand Up @@ -282,7 +282,7 @@ export class ServerGroupBasicSettings
<div className="form-group">
<div className="col-md-3 sm-label-right">Image Source</div>
<div className="col-md-7" style={{ marginTop: '5px' }}>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(values.viewState.imageSourceText) }} />
<Markdown tag="span" message={values.viewState.imageSourceText} />
</div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import DOMPurify from 'dompurify';
import Select, { Option } from 'react-select';
import { defaultsDeep, unset } from 'lodash';

Expand All @@ -8,6 +7,7 @@ import {
IDeploymentStrategy,
IDeploymentStrategyAdditionalFieldsProps,
IServerGroupCommand,
Markdown,
} from '@spinnaker/core';

import { IRedBlackCommand } from 'cloudfoundry/deploymentStrategy/strategies/redblack/redblack.strategy';
Expand Down Expand Up @@ -167,10 +167,10 @@ export class CloudFoundryDeploymentStrategySelector extends React.Component<
return (
<div className="body-regular">
<strong>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(option.label) }} />
<Markdown tag="span" message={option.label} />
</strong>
<div>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(option.description) }} />
<Markdown tag="span" message={option.description} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
IAccount,
IDeploymentStrategy,
IWizardPageComponent,
Markdown,
ReactSelectInput,
StageConstants,
} from '@spinnaker/core';
Expand All @@ -19,7 +20,6 @@ import { ICloudFoundryCreateServerGroupCommand } from 'cloudfoundry/serverGroup/
import { FormikAccountRegionClusterSelector } from 'cloudfoundry/presentation';

import 'cloudfoundry/common/cloudFoundry.less';
import DOMPurify from 'dompurify';

export interface ICloudFoundryCloneSettingsProps {
formik: FormikProps<ICloudFoundryCreateServerGroupCommand>;
Expand Down Expand Up @@ -52,10 +52,10 @@ export class CloudFoundryServerGroupCloneSettings
return (
<div className="body-regular">
<strong>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(option.label) }} />
<Markdown tag="span" message={option.label} />
</strong>
<div>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(option.description) }} />
<Markdown tag="span" message={option.description} />
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/modules/core/src/cancelModal/CancelModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class CancelModal extends React.Component<ICancelModalProps, ICancelModal
initialValues={{}}
onSubmit={this.submitConfirmation}
render={() => {
const wrappedBody = body ? <Markdown message={body} /> : null;
const wrappedBody = body ? <Markdown message={body} trim={true} /> : null;

return (
<Form className="form-horizontal">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React from 'react';
import DOMPurify from 'dompurify';
import Select, { Option } from 'react-select';
import { unset } from 'lodash';

import { HelpField } from 'core/help/HelpField';
import { Markdown } from 'core/presentation';
import { IServerGroupCommand } from 'core/serverGroup';

import {
DeploymentStrategyRegistry,
IDeploymentStrategy,
IDeploymentStrategyAdditionalFieldsProps,
} from './deploymentStrategy.registry';
import { HelpField } from 'core/help/HelpField';

export interface IDeploymentStrategySelectorProps {
command: IServerGroupCommand;
Expand Down Expand Up @@ -122,10 +122,10 @@ export class DeploymentStrategySelector extends React.Component<
return (
<div className="body-regular">
<strong>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(option.label) }} />
<Markdown tag="span" message={option.label} />
</strong>
<div>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(option.description) }} />
<Markdown tag="span" message={option.description} />
</div>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions app/scripts/modules/core/src/help/HelpField.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React from 'react';
import ReactGA from 'react-ga';
import DOMPurify from 'dompurify';
import { isUndefined } from 'lodash';

import { HelpContentsRegistry, HelpTextExpandedContext } from 'core/help';
import { HoverablePopover, Placement } from 'core/presentation';
import { HoverablePopover, Markdown, Placement } from 'core/presentation';

export interface IHelpFieldProps {
id?: string;
Expand All @@ -22,9 +21,10 @@ function HelpFieldContents(props: Pick<IHelpFieldProps, 'id' | 'fallback' | 'con
if (id && !contentString) {
contentString = HelpContentsRegistry.getHelpField(id) || fallback;
}
console.warn(contentString);

const config = { ADD_ATTR: ['target'] }; // allow: target="_blank"
return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(contentString, config) }} />;
return <Markdown message={contentString} options={config} trim={true} />;
}

export function HelpField(props: IHelpFieldProps) {
Expand Down
37 changes: 19 additions & 18 deletions app/scripts/modules/core/src/pagerDuty/Pager.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import DOMPurify from 'dompurify';
import { UISref } from '@uirouter/react';
import SearchApi from 'js-worker-search';
import { groupBy } from 'lodash';
Expand All @@ -24,6 +23,7 @@ import { relativeTime } from 'core/utils/timeFormatters';
import { IOnCall, IPagerDutyService, PagerDutyReader } from './pagerDuty.read.service';
import { ReactInjector } from 'core/reactShims';
import { SETTINGS } from 'core/config';
import { Markdown } from 'core/presentation';

import './pager.less';

Expand Down Expand Up @@ -315,8 +315,9 @@ export class Pager extends React.Component<IPagerProps, IPagerState> {
title={service.name}
href={`https://${this.state.accountName}.pagerduty.com/services/${service.id}`}
target="_blank"
dangerouslySetInnerHTML={{ __html: this.highlight(service.name) }}
/>
>
<Markdown tag="span" message={this.highlight(service.name)} />
</a>
</div>
);
};
Expand All @@ -337,7 +338,9 @@ export class Pager extends React.Component<IPagerProps, IPagerState> {
return (
<li key={app.name}>
<UISref to="home.applications.application.insight.clusters" params={{ application: app.name }}>
<a className="clickable" dangerouslySetInnerHTML={{ __html: this.highlight(displayName) }} />
<a>
<Markdown message={this.highlight(displayName)} tag="span" />
</a>
</UISref>
</li>
);
Expand All @@ -362,9 +365,9 @@ export class Pager extends React.Component<IPagerProps, IPagerState> {
const match = this.state.filterString || this.state.app;
if (match) {
const re = new RegExp(match, 'gi');
return DOMPurify.sanitize(text.replace(re, '<span class="highlighted">$&</span>'));
return text.replace(re, '<span class="highlighted">$&</span>');
}
return DOMPurify.sanitize(text);
return text;
}

private onCallRenderer = (data: TableCellProps): React.ReactNode => {
Expand All @@ -387,12 +390,9 @@ export class Pager extends React.Component<IPagerProps, IPagerState> {
{onCalls[Number(level)]
.filter(user => !user.name.includes('ExcludeFromAudit'))
.map((user, index) => (
<a
key={index}
target="_blank"
href={user.url}
dangerouslySetInnerHTML={{ __html: this.highlight(user.name) }}
/>
<a key={index} target="_blank" href={user.url}>
<Markdown tag="span" message={this.highlight(user.name)} />
</a>
))}
</div>
</div>
Expand Down Expand Up @@ -476,12 +476,13 @@ export class Pager extends React.Component<IPagerProps, IPagerState> {

private rowClicked = (info: RowMouseEventHandlerParams): void => {
// Don't change selection if clicking a link...
if (!['A', 'I'].includes((info.event.target as any).tagName)) {
const service: IPagerDutyService = (info.rowData as any).service;
if (service.status !== 'disabled') {
const flippedValue = !this.state.selectedKeys.get(service.integration_key);
this.selectedChanged(service, flippedValue);
}
if ((info.event.target as HTMLElement).closest('A') !== null) {
return;
}
const service: IPagerDutyService = (info.rowData as any).service;
if (service.status !== 'disabled') {
const flippedValue = !this.state.selectedKeys.get(service.integration_key);
this.selectedChanged(service, flippedValue);
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from 'react';
import ReactGA from 'react-ga';
import DOMPurify from 'dompurify';
import classNames from 'classnames';
import { get } from 'lodash';

import { IExecutionStageSummary } from 'core/domain';
import { GroupExecutionPopover } from 'core/pipeline/config/stages/group/GroupExecutionPopover';
import { LabelComponent } from 'core/presentation';
import { LabelComponent, Markdown } from 'core/presentation';
import { Popover } from 'core/presentation/Popover';

import { IPipelineGraphNode } from './pipelineGraph.service';
Expand Down Expand Up @@ -70,7 +69,9 @@ export class PipelineGraphNode extends React.Component<IPipelineGraphNodeProps>
<ul>
{node.hasWarnings &&
node.warnings.messages.map(message => (
<li key={message} dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(message) }} />
<li key={message}>
<Markdown message={message} trim={true} />
</li>
))}
</ul>
</div>
Expand Down
4 changes: 4 additions & 0 deletions app/scripts/modules/core/src/presentation/Markdown.less
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
margin-top: 4px;
}
}

span.Markdown p {
display: contents;
}
15 changes: 12 additions & 3 deletions app/scripts/modules/core/src/presentation/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,23 @@ export interface IMarkdownProps {
/** optional tag */
tag?: string;

/** trim the contents before generating markdown; markdown treats four leading spaces as a <pre> tag, so if you are
* using string templates and the content starts on a new line, you probably want this set to true */
trim?: boolean;

/** The className(s) to apply to the tag (.Markdown class is always applied) */
className?: string;

/** Options passed to DOMPurify. Examples: https://github.com/cure53/DOMPurify/tree/master/demos#what-is-this */
options?: IDOMPurifyConfig;
}

/**
* Renders markdown into a div (or some other tag)
* Extra props are passed through to the rendered tag
*/
export function Markdown(props: IMarkdownProps) {
const { message, tag: tagProp, className: classNameProp, tag = 'div', ...rest } = props;
const { message, tag: tagProp, className: classNameProp, trim, tag = 'div', ...rest } = props;

const parser = React.useMemo(() => new Parser(), []);
const renderer = React.useMemo(() => new HtmlRenderer(), []);
Expand All @@ -31,10 +38,12 @@ export function Markdown(props: IMarkdownProps) {
return null;
}

const parseable = trim ? message.trim() : message;

const restProps = rest as React.DOMAttributes<any>;
const parsed = parser.parse(message.toString());
const parsed = parser.parse(parseable.toString());
const rendered = renderer.render(parsed);
restProps.dangerouslySetInnerHTML = { __html: DOMPurify.sanitize(rendered) };
restProps.dangerouslySetInnerHTML = { __html: DOMPurify.sanitize(rendered, props.options) };

return React.createElement(tag, { ...restProps, className });
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DOMPurify from 'dompurify';
import React from 'react';

import { AccountTag } from 'core/account';
import { Markdown } from 'core/presentation';

import './searchResult.less';

Expand All @@ -17,7 +17,7 @@ export class SearchResult extends React.Component<ISearchResultProps> {
return (
<span className="search-result">
{account && <AccountTag account={account} />}
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(displayName) }} />
<Markdown tag="span" message={displayName} />
</span>
);
}
Expand Down
7 changes: 2 additions & 5 deletions app/scripts/modules/core/src/widgets/notifier/Notifier.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import DOMPurify from 'dompurify';
import { Subscription } from 'rxjs';

import { INotifier, NotifierService } from 'core/widgets';
import { Markdown } from 'core/presentation';

export interface INotifierState {
messages: INotifier[];
Expand Down Expand Up @@ -42,10 +42,7 @@ export class Notifier extends React.Component<{}, INotifierState> {

private makeNotification = (message: INotifier) => (
<div key={message.key} className="user-notification horizontal space-around">
<div
className="message"
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(message.body, { ADD_ATTR: ['onclick'] }) }}
/>
<Markdown className="message" message={message.body} options={{ ADD_ATTR: ['onclick'] }} />
<button className="btn btn-link close-notification" role="button" onClick={() => this.dismiss(message.key)}>
<span className="fa fa-times" />
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import DOMPurify from 'dompurify';
import React from 'react';
import { cloneDeep, map, set, split } from 'lodash';
import Select, { Creatable, Option } from 'react-select';

import { IAccountDetails, IDeploymentStrategy, StageConfigField } from '@spinnaker/core';
import { IAccountDetails, IDeploymentStrategy, Markdown, StageConfigField } from '@spinnaker/core';

import { ManifestKindSearchService } from 'kubernetes/v2/manifest/ManifestKindSearch';
import { rolloutStrategies } from 'kubernetes/v2/rolloutStrategy';
Expand Down Expand Up @@ -81,10 +80,10 @@ export class ManifestDeploymentOptions extends React.Component<
return (
<div className="body-regular">
<strong>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(option.label) }} />
<Markdown tag="span" message={option.label} />
</strong>
<div>
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(option.description) }} />
<Markdown tag="span" message={option.description} />
</div>
</div>
);
Expand Down

0 comments on commit 6548872

Please sign in to comment.