From d9211377dd6c5c4289f34575ada6a59122a7461b Mon Sep 17 00:00:00 2001 From: Tomas Bures Date: Sun, 13 Aug 2017 11:32:31 +0200 Subject: [PATCH] Options always shown below the group no matter how the list is sorted XSS protection for tables and trees --- client/src/lib/table.js | 15 +++++++++++- client/src/lib/tree.js | 19 +++++++++++---- client/src/lists/List.js | 2 +- client/src/lists/fields/CUD.js | 42 ++++++++++++++++++++++++++------- client/src/lists/fields/List.js | 4 +++- models/fields.js | 35 +++++++++++++++++++++++---- 6 files changed, 96 insertions(+), 21 deletions(-) diff --git a/client/src/lib/table.js b/client/src/lib/table.js index 6a7cfb19..807710f0 100644 --- a/client/src/lib/table.js +++ b/client/src/lib/table.js @@ -239,8 +239,21 @@ class Table extends Component { type: 'html', createdCell: createdCellFn }); + } - // FIXME, sift all columns through renderToStaticMarkup in order to sanitize the HTML + // XSS protection + for (const column of columns) { + const originalRender = column.render; + column.render = (data, ...rest) => { + if (originalRender) { + const markup = originalRender(data, ...rest); + return ReactDOMServer.renderToStaticMarkup(
{markup}
); + } else { + return ReactDOMServer.renderToStaticMarkup(
{data}
) + } + }; + + column.title = ReactDOMServer.renderToStaticMarkup(
{column.title}
); } const dtOptions = { diff --git a/client/src/lib/tree.js b/client/src/lib/tree.js index 4ee5821c..ac4dabde 100644 --- a/client/src/lib/tree.js +++ b/client/src/lib/tree.js @@ -86,6 +86,17 @@ class TreeTable extends Component { return this.props.selection !== nextProps.selection || this.state.treeData != nextState.treeData; } + // XSS protection + sanitizeTreeData(unsafeData) { + const data = unsafeData.slice(); + for (const entry of data) { + entry.title = ReactDOMServer.renderToStaticMarkup(
{entry.title}
) + entry.description = ReactDOMServer.renderToStaticMarkup(
{entry.description}
) + entry.children = this.sanitizeTreeData(entry.children); + } + return data; + } + componentDidMount() { if (!this.props.data && this.props.dataUrl) { this.loadData(this.props.dataUrl); @@ -109,10 +120,8 @@ class TreeTable extends Component { let tdIdx = 1; - // FIXME, sift title through renderToStaticMarkup in order to sanitize the HTML - if (this.props.withDescription) { - const descHtml = ReactDOMServer.renderToStaticMarkup(
{node.data.description}
); + const descHtml = node.data.description; // This was already sanitized in sanitizeTreeData when the data was loaded tdList.eq(tdIdx).html(descHtml); tdIdx += 1; } @@ -142,7 +151,7 @@ class TreeTable extends Component { icon: false, autoScroll: true, scrollParent: jQuery(this.domTableContainer), - source: this.state.treeData, + source: this.sanitizeTreeData(this.state.treeData), table: { nodeColumnIdx: 0 }, @@ -156,7 +165,7 @@ class TreeTable extends Component { } componentDidUpdate() { - this.tree.reload(this.state.treeData); + this.tree.reload(this.sanitizeTreeData(this.state.treeData)); this.updateSelection(); } diff --git a/client/src/lists/List.js b/client/src/lists/List.js index 9d696063..340ff883 100644 --- a/client/src/lists/List.js +++ b/client/src/lists/List.js @@ -71,7 +71,7 @@ export default class List extends Component { const columns = [ { data: 1, title: t('Name') }, - { data: 2, title: t('ID'), render: data => `${data}` }, + { data: 2, title: t('ID'), render: data => {data} }, { data: 3, title: t('Subscribers') }, { data: 4, title: t('Description') }, { data: 5, title: t('Namespace') } diff --git a/client/src/lists/fields/CUD.js b/client/src/lists/fields/CUD.js index b3289d24..834b80b1 100644 --- a/client/src/lists/fields/CUD.js +++ b/client/src/lists/fields/CUD.js @@ -67,7 +67,7 @@ export default class CUD extends Component { const getOrderOptions = fld => { return [ {key: 'none', label: t('Not visible')}, - ...flds.data.filter(x => (!this.props.entity || x.id !== this.props.entity.id) && x[fld] !== null).sort((x, y) => x[fld] - y[fld]).map(x => ({ key: x.id, label: `${x.name} (${this.fieldTypes[x.type].label})`})), + ...flds.data.filter(x => (!this.props.entity || x.id !== this.props.entity.id) && x[fld] !== null && x.type !== 'option').sort((x, y) => x[fld] - y[fld]).map(x => ({ key: x.id.toString(), label: `${x.name} (${this.fieldTypes[x.type].label})`})), {key: 'end', label: t('End of list')} ]; }; @@ -82,6 +82,8 @@ export default class CUD extends Component { componentDidMount() { if (this.props.entity) { this.getFormValuesFromEntity(this.props.entity, data => { + data.settings = data.settings || {}; + if (data.default_value === null) { data.default_value = ''; } @@ -113,6 +115,10 @@ export default class CUD extends Component { data.dateFormat = data.settings.dateFormat; break; } + + data.orderListBefore = data.orderListBefore.toString(); + data.orderSubscribeBefore = data.orderSubscribeBefore.toString(); + data.orderManageBefore = data.orderManageBefore.toString(); }); } else { @@ -177,9 +183,17 @@ export default class CUD extends Component { state.setIn(['default_value', 'error'], null); } - let enumOptionErrors; - if ((type === 'radio-enum' || type === 'dropdown-enum') && (enumOptionErrors = this.parseEnumOptions(state.getIn(['enumOptions', 'value'])).errors)) { - state.setIn(['enumOptions', 'error'],
{enumOptionErrors.map((err, idx) =>
{err}
)}
); + if (type === 'radio-enum' || type === 'dropdown-enum') { + const enumOptions = this.parseEnumOptions(state.getIn(['enumOptions', 'value'])); + if (enumOptions.errors) { + state.setIn(['enumOptions', 'error'],
{enumOptions.errors.map((err, idx) =>
{err}
)}
); + } else { + state.setIn(['enumOptions', 'error'], null); + + if (defaultValue !== '' && !(defaultValue in enumOptions.options)) { + state.setIn(['default_value', 'error'], t('Default value is not one of the allowed options')); + } + } } else { state.setIn(['enumOptions', 'error'], null); } @@ -271,6 +285,14 @@ export default class CUD extends Component { delete data.renderTemplate; delete data.enumOptions; delete data.dateFormat; + + if (data.type === 'option') { + data.orderListBefore = data.orderSubscribeBefore = data.orderManageBefore = 'none'; + } else { + data.orderListBefore = Number.parseInt(data.orderListBefore) || data.orderListBefore; + data.orderSubscribeBefore = Number.parseInt(data.orderSubscribeBefore) || data.orderSubscribeBefore; + data.orderManageBefore = Number.parseInt(data.orderManageBefore) || data.orderManageBefore; + } }); if (submitSuccessful) { @@ -445,11 +467,13 @@ export default class CUD extends Component { {fieldSettings} -
- - - -
+ {type !== 'option' && +
+ + + +
+ }