Skip to content

Commit

Permalink
fix: allow configuring the XSS protection behavior (#1010)
Browse files Browse the repository at this point in the history
fixes #846
  • Loading branch information
dennissterzenbach authored Apr 10, 2021
1 parent 6cbae49 commit 47b8bed
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 33 deletions.
55 changes: 40 additions & 15 deletions docs/timeline/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,9 @@ <h3 id="items">Items</h3>
<td>selectable</td>
<td>Boolean</td>
<td>no</td>
<td>Ability to enable/disable selectability for specific items.
<td>Ability to enable/disable selectability for specific items.
Defaults to <code>true</code>. Does not override the timeline's
<code>selectable</code> configuration option.
<code>selectable</code> configuration option.
</td>
</tr>
<tr>
Expand Down Expand Up @@ -466,7 +466,7 @@ <h3 id="groups">Groups</h3>
<td>subgroupStack</td>
<td>Object or Boolean</td>
<td>none</td>
<td>Enables stacking within individual subgroups. Example: <code>{'subgroup0': true, 'subgroup1': false, 'subgroup2': true}</code>
<td>Enables stacking within individual subgroups. Example: <code>{'subgroup0': true, 'subgroup1': false, 'subgroup2': true}</code>
For each subgroup where stacking is enabled, items will be stacked on top of each other within that subgroup such that they do no overlap.
If set to <code>true</code> all subgroups will be stacked.
If a value was specified for the <code>order</code> parameter in the options, that ordering will be used when stacking the items.
Expand All @@ -476,8 +476,8 @@ <h3 id="groups">Groups</h3>
<td>subgroupVisibility</td>
<td>Object</td>
<td>none</td>
<td>Ability to hide/show specific subgroups. Example: <code>{'hiddenSubgroup0': false, 'subgroup1': true, 'subgroup2': true}</code>
If a subgroup is missing from the object, it will default as true (visible).
<td>Ability to hide/show specific subgroups. Example: <code>{'hiddenSubgroup0': false, 'subgroup1': true, 'subgroup2': true}</code>
If a subgroup is missing from the object, it will default as true (visible).
</td>
</tr>
<tr>
Expand Down Expand Up @@ -933,15 +933,15 @@ <h2 id="Configuration_Options">Configuration Options</h2>
<td>Callback function triggered when an item is about to be added: when the user double taps an empty space in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.add</code> are set <code>true</code>.
</td>
</tr>

<tr>
<td>onAddGroup</td>
<td>function</td>
<td>none</td>
<td>Callback function triggered when a group is about to be added. The signature and semantics are the same as for <code>onAdd</code>.
</td>
</tr>

<tr>
<td>onDropObjectOnItem</td>
<td>function</td>
Expand All @@ -957,55 +957,55 @@ <h2 id="Configuration_Options">Configuration Options</h2>
<td>Callback function triggered when the timeline is initially drawn. This function fires once per timeline creation.
</td>
</tr>

<tr>
<td>onMove</td>
<td>function</td>
<td>none</td>
<td>Callback function triggered when an item has been moved: after the user has dragged the item to an other position. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.updateTime</code> or <code>editable.updateGroup</code> are set <code>true</code>.
</td>
</tr>

<tr>
<td>onMoveGroup</td>
<td>function</td>
<td>none</td>
<td>Callback function triggered when a group has been moved: after the user has dragged the group to an other position. The signature and semantics are the same as for <code>onMove</code>.
</td>
</tr>

<tr>
<td>onMoving</td>
<td>function</td>
<td>none</td>
<td>Callback function triggered repeatedly when an item is being moved. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.updateTime</code> or <code>editable.updateGroup</code> are set <code>true</code>.
</td>
</tr>

<tr>
<td>onRemove</td>
<td>function</td>
<td>none</td>
<td>Callback function triggered when an item is about to be removed: when the user tapped the delete button on the top right of a selected item. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.remove</code> are set <code>true</code>.
</td>
</tr>

<tr>
<td>onRemoveGroup</td>
<td>function</td>
<td>none</td>
<td>Callback function triggered when a group is about to be removed. The signature and semantics are the same as for <code>onRemove</code>.
</td>
</tr>

<tr>
<td>onUpdate</td>
<td>function</td>
<td>none</td>
<td>Callback function triggered when an item is about to be updated, when the user double taps an item in the Timeline. See section <a href="#Editing_Items">Editing Items</a> for more information. Only applicable when both options <code>selectable</code> and <code>editable.updateTime</code> or <code>editable.updateGroup</code> are set <code>true</code>.
</td>
</tr>

<tr>
<td>order</td>
<td>function</td>
Expand Down Expand Up @@ -1336,6 +1336,31 @@ <h2 id="Configuration_Options">Configuration Options</h2>
<td>The width of the timeline in pixels or as a percentage.</td>
</tr>

<tr class='toggle collapsible' onclick="toggleTable('optionTable','xss', this);">
<td><span parent="xss" class="right-caret"></span> xss</td>
<td>Object</td>
<td>none</td>
<td>Configure the XSS protection behavior of Timeline, which is always enabled by default. This means that most attributes and HTML elements are either removed or escaped before output.</td>
</tr>
<tr parent="xss" class="hidden">
<td class="indent">xss.disabled</td>
<td>Boolean</td>
<td>undefined</td>
<td>Explicitly set this option to <code>true</code> to completely disable Timeline's XSS protection.
<p>Note: Please make sure you install protection against XSS vulnerabilities yourself!</p>
</td>
</tr>

<tr parent="xss" class="hidden">
<td class="indent">xss.filterOptions</td>
<td>XSS.IFilterXSSOptions</td>
<td>undefined</td>
<td>This allows to customize whitelisting of HTMLElements, attributes and different handler functions. For more details, please refer to
<a href="https://github.com/leizongmin/js-xss#custom-filter-rules" target="_blank" rel="noopener noreferer">the documentation of js-xss</a> and
<a href="https://github.com/leizongmin/js-xss/blob/master/typings/xss.d.ts#L12" target="_blank" rel="noopener noreferer">the related typings</a>.
</td>
</tr>

<tr>
<td>zoomable</td>
<td>boolean</td>
Expand Down Expand Up @@ -1640,7 +1665,7 @@ <h2 id="Methods">Methods</h2>
<ul>
<li><code>animation: boolean or {duration: number, easingFunction: string}</code><br>If true (default) or an Object, the range is animated smoothly to the new window. An object can be provided to specify duration and easing function. Default duration is 500 ms, and default easing function is <code>'easeInOutQuad'</code>. Available easing functions: <code>"linear"</code>, <code>"easeInQuad"</code>, <code>"easeOutQuad"</code>, <code>"easeInOutQuad"</code>, <code>"easeInCubic"</code>, <code>"easeOutCubic"</code>, <code>"easeInOutCubic"</code>, <code>"easeInQuart"</code>, <code>"easeOutQuart"</code>, <code>"easeInOutQuart"</code>, <code>"easeInQuint"</code>, <code>"easeOutQuint"</code>, <code>"easeInOutQuint"</code>.</li>
</ul>
A callback <code>function</code> can be passed as an optional parameter. This function will be called at the end of setWindow function.
A callback <code>function</code> can be passed as an optional parameter. This function will be called at the end of setWindow function.
</td>
</tr>

Expand Down
21 changes: 11 additions & 10 deletions lib/timeline/Timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export default class Timeline extends Core {
moment,
};
this.options = util.deepExtend({}, this.defaultOptions);
options && util.setupXSSProtection(options.xss);

// Create the DOM, props, and emitter
this._create(container);
Expand Down Expand Up @@ -211,7 +212,7 @@ export default class Timeline extends Core {
}
}

if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end)
if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end)
|| me.options.rollingMode)) {
me.initialDrawDone = true;
me.itemSet.initialDrawDone = true;
Expand Down Expand Up @@ -310,7 +311,7 @@ export default class Timeline extends Core {
*/
setItems(items) {
this.itemsDone = false;

// convert to type DataSet when needed
let newDataSet;
if (!items) {
Expand Down Expand Up @@ -341,14 +342,14 @@ export default class Timeline extends Core {
// convert to type DataSet when needed
let newDataSet;
const filter = group => group.visible !== false;

if (!groups) {
newDataSet = null;
}
else {
// If groups is array, turn to DataSet & build dataview from that
if (Array.isArray(groups)) groups = new DataSet(groups);

newDataSet = new DataView(groups,{filter});
}

Expand Down Expand Up @@ -483,7 +484,7 @@ export default class Timeline extends Core {
// The redraw shifted elements, so reset the animation to correct
initialVerticalScroll = verticalScroll;
startPos = me._getScrollTop() * -1;
}
}

const from = startPos;
const to = initialVerticalScroll.scrollOffset;
Expand Down Expand Up @@ -512,7 +513,7 @@ export default class Timeline extends Core {
// Double check we ended at the proper scroll position
setFinalVerticalPosition();

// Let the redraw settle and finalize the position.
// Let the redraw settle and finalize the position.
setTimeout(setFinalVerticalPosition, 100);
};

Expand All @@ -528,7 +529,7 @@ export default class Timeline extends Core {
initialVerticalScroll = { shouldScroll: false, scrollOffset: -1, itemTop: -1 };
}

this.range.setRange(middle - interval / 2, middle + interval / 2, { animation }, finalVerticalCallback, verticalAnimationFrame);
this.range.setRange(middle - interval / 2, middle + interval / 2, { animation }, finalVerticalCallback, verticalAnimationFrame);
}
}

Expand Down Expand Up @@ -689,7 +690,7 @@ export default class Timeline extends Core {
const centerContainerRect = this.dom.centerContainer.getBoundingClientRect();
const x = this.options.rtl ? centerContainerRect.right - clientX : clientX - centerContainerRect.left;
const y = clientY - centerContainerRect.top;

const item = this.itemSet.itemFromTarget(event);
const group = this.itemSet.groupFromTarget(event);
const customTime = CustomTime.customTimeFromTarget(event);
Expand Down Expand Up @@ -800,12 +801,12 @@ function getItemVerticalScroll(timeline, item) {

const itemsetHeight = timeline.options.rtl ? timeline.props.rightContainer.height : timeline.props.leftContainer.height;
const contentHeight = timeline.props.center.height;

const group = item.parent;
let offset = group.top;
let shouldScroll = true;
const orientation = timeline.timeAxis.options.orientation.axis;

const itemTop = () => {
if (orientation == "bottom") {
return group.height - item.top - item.height;
Expand Down
14 changes: 11 additions & 3 deletions lib/timeline/optionsTimeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ let allOptions = {
showWeekScale: { 'boolean': bool},
stack: { 'boolean': bool},
stackSubgroups: { 'boolean': bool},
cluster: {
cluster: {
maxItems: {'number': number, 'undefined': 'undefined'},
titleTemplate: {'string': string, 'undefined': 'undefined'},
clusterCriteria: { 'function': 'function', 'undefined': 'undefined'},
Expand Down Expand Up @@ -188,7 +188,14 @@ let allOptions = {
zoomFriction: {number},
zoomMax: {number},
zoomMin: {number},

xss: {
disabled: { boolean: bool },
filterOptions: {
__any__: { any },
__type__: { object }
},
__type__: { object }
},
__type__: {object}
};

Expand Down Expand Up @@ -290,7 +297,8 @@ let configureOptions = {
zoomable: true,
zoomKey: ['ctrlKey', 'altKey', 'shiftKey', 'metaKey', ''],
zoomMax: [315360000000000, 10, 315360000000000, 1],
zoomMin: [10, 10, 315360000000000, 1]
zoomMin: [10, 10, 315360000000000, 1],
xss: { disabled: false }
}
};

Expand Down
48 changes: 43 additions & 5 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getType, isNumber, isString } from "vis-util/esnext";
import { DataSet, createNewDataPipeFrom } from "vis-data/esnext";

import moment from "moment";
import xss from 'xss';
import xssFilter from 'xss';

// parse ASP.Net Date pattern,
// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
Expand Down Expand Up @@ -87,13 +87,13 @@ export function convert(object, type) {
if (match) {
// object is an ASP date
return moment(Number(match[1])); // parse number
}
}
match = NumericRegex.exec(object);

if (match) {
return moment(Number(object));
}

return moment(object); // parse string
} else {
throw new TypeError(
Expand Down Expand Up @@ -225,8 +225,46 @@ export function typeCoerceDataSet(
};
}

export default {
// Configure XSS protection
const setupXSSCleaner = (options) => {
const customXSS = new xssFilter.FilterXSS(options);
return (string) => customXSS.process(string);
};
const setupNoOpCleaner = (string) => string;

// when nothing else is configured: filter XSS with the lib's default options
let configuredXSSProtection = setupXSSCleaner();

const setupXSSProtection = (options) => {
// No options? Do nothing.
if (!options) {
return;
}

// Disable XSS protection completely on request
if (options.disabled === true) {
configuredXSSProtection = setupNoOpCleaner;
console.warn('You disabled XSS protection for vis-Timeline. I sure hope you know what you\'re doing!');
} else {
// Configure XSS protection with some custom options.
// For a list of valid options check the lib's documentation:
// https://github.com/leizongmin/js-xss#custom-filter-rules
if (options.filterOptions) {
configuredXSSProtection = setupXSSCleaner(options.filterOptions);
}
}
};

const availableUtils = {
...util,
convert,
xss
setupXSSProtection
};

Object.defineProperty(availableUtils, 'xss', {
get: function() {
return configuredXSSProtection;
}
})

export default availableUtils;
6 changes: 6 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ export interface TimelineTooltipOption {
template?: (item: TimelineItem, editedData?: TimelineItem) => string;
}

export interface TimelineXSSProtectionOption {
disabled: boolean;
filterOptions?: XSS.IFilterXSSOptions;
}

export type TimelineOptionsConfigureFunction = (option: string, path: string[]) => boolean;
export type TimelineOptionsConfigureType = boolean | TimelineOptionsConfigureFunction;
export type TimelineOptionsDataAttributesType = boolean | string | string[];
Expand Down Expand Up @@ -312,6 +317,7 @@ export interface TimelineOptions {
zoomFriction?: number;
zoomMax?: number;
zoomMin?: number;
xss?: TimelineXSSProtectionOption;
}

/**
Expand Down

0 comments on commit 47b8bed

Please sign in to comment.