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' &&
+
+ }