From 38aafbce6cf6cd63e38a949ed31dde5e15b2a5e4 Mon Sep 17 00:00:00 2001 From: Vikas Awaghade <67629551+vikas-cldcvr@users.noreply.github.com> Date: Tue, 19 Dec 2023 17:22:46 +0530 Subject: [PATCH] =?UTF-8?q?FLOW-1037=20lineage=20node=20template=20renderi?= =?UTF-8?q?ng=20logic=20updated=20to=20fix=20html=20i=E2=80=A6=20(#214)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FLOW-1037 lineage node template rendering logic updated to fix html injection * FLOW-1037 null svg element check added * FLOW-1037 rendering error updated --- packages/flow-lineage/CHANGELOG.md | 6 + packages/flow-lineage/package.json | 2 +- .../components/f-lineage/draw/draw-nodes.ts | 10 +- .../f-lineage/draw/hot-reload-proxies.ts | 16 +- .../src/components/f-lineage/f-lineage.ts | 71 ++-- .../htmlinjection-test.stories.ts | 349 ++++++++++++++++++ stories/flow-lineage/lineage-properties.mdx | 30 +- 7 files changed, 424 insertions(+), 60 deletions(-) create mode 100644 stories/flow-lineage/htmlinjection-test.stories.ts diff --git a/packages/flow-lineage/CHANGELOG.md b/packages/flow-lineage/CHANGELOG.md index ca14cc787..c53864578 100644 --- a/packages/flow-lineage/CHANGELOG.md +++ b/packages/flow-lineage/CHANGELOG.md @@ -2,6 +2,12 @@ # Change Log +## [3.1.1] - 2023-12-19 + +### Patch Changes + +- 🔄 Updated the template rendering logic of d3's `foreignObject` for enhanced security against HTML injection. 🛡️ + ## [3.1.0] - 2023-11-27 ### Minor Changes diff --git a/packages/flow-lineage/package.json b/packages/flow-lineage/package.json index 2397405c5..91c807eb3 100644 --- a/packages/flow-lineage/package.json +++ b/packages/flow-lineage/package.json @@ -1,6 +1,6 @@ { "name": "@cldcvr/flow-lineage", - "version": "3.1.0", + "version": "3.1.1", "description": "Lineage dependency for flow design system", "module": "dist/flow-lineage.es.js", "main": "dist/flow-lineage.cjs.js", diff --git a/packages/flow-lineage/src/components/f-lineage/draw/draw-nodes.ts b/packages/flow-lineage/src/components/f-lineage/draw/draw-nodes.ts index 8d7fb325f..4ed017325 100644 --- a/packages/flow-lineage/src/components/f-lineage/draw/draw-nodes.ts +++ b/packages/flow-lineage/src/components/f-lineage/draw/draw-nodes.ts @@ -164,8 +164,8 @@ export default function drawNodes(params: DrawLineageParams) { element.reDrawChunk(+pageNo, d.level); } }) - .html(node => { - return element.doTemplateHotUpdate(node); + .each(function (d) { + element.doTemplateHotUpdate(d, this as unknown as HTMLElement); }); /** @@ -355,10 +355,8 @@ export default function drawNodes(params: DrawLineageParams) { d.fRightClick(event, d); } }) - /* eslint-disable @typescript-eslint/ban-ts-comment */ - //@ts-ignore - .html(node => { - return element.doTemplateHotUpdate(node, true); + .each(function (d) { + element.doTemplateHotUpdate(d, this as unknown as HTMLElement, true); }); drawLinks({ diff --git a/packages/flow-lineage/src/components/f-lineage/draw/hot-reload-proxies.ts b/packages/flow-lineage/src/components/f-lineage/draw/hot-reload-proxies.ts index b591f3d5f..18b31a25b 100644 --- a/packages/flow-lineage/src/components/f-lineage/draw/hot-reload-proxies.ts +++ b/packages/flow-lineage/src/components/f-lineage/draw/hot-reload-proxies.ts @@ -23,8 +23,12 @@ export default function getProxies(element: FLineage) { if (nodeElement) { d3.select(element.svg) .select(`#${target.__id__}-foreign-object`) - .html(() => { - return element.doTemplateHotUpdate(nodeElement, nodeElement.isChildren); + .call(function (d) { + element.doTemplateHotUpdate( + nodeElement, + d.node() as unknown as HTMLElement, + nodeElement.isChildren + ); }); } } @@ -60,8 +64,12 @@ export default function getProxies(element: FLineage) { nodeElement.fData = target[key]; d3.select(element.svg) .select(`#${target.__id__}-foreign-object`) - .html(() => { - return element.doTemplateHotUpdate(nodeElement, nodeElement.isChildren); + .call(function (d) { + element.doTemplateHotUpdate( + nodeElement, + d.node() as unknown as HTMLElement, + nodeElement.isChildren + ); }); } } diff --git a/packages/flow-lineage/src/components/f-lineage/f-lineage.ts b/packages/flow-lineage/src/components/f-lineage/f-lineage.ts index 2a95475c9..0182de51e 100644 --- a/packages/flow-lineage/src/components/f-lineage/f-lineage.ts +++ b/packages/flow-lineage/src/components/f-lineage/f-lineage.ts @@ -1,4 +1,4 @@ -import { html, PropertyValues, unsafeCSS } from "lit"; +import { html, PropertyValues, render, unsafeCSS } from "lit"; import { customElement, property, query, queryAssignedElements } from "lit/decorators.js"; import eleStyle from "./f-lineage.scss?inline"; import globalStyle from "./f-lineage-global.scss?inline"; @@ -20,7 +20,7 @@ import lowlightPath from "./highlight/lowlight-path"; import createHierarchy from "./create/create-hierarchy"; import { FButton, FDiv, FIcon, FIconButton, FPopover, FText } from "@cldcvr/flow-core"; import { FRoot } from "@cldcvr/flow-core"; -import { debounce, getComputedHTML } from "../../utils"; +import { debounce } from "../../utils"; import getProxies from "./draw/hot-reload-proxies"; import { ref, createRef, Ref } from "lit/directives/ref.js"; import { injectCss } from "@cldcvr/flow-core-config"; @@ -613,26 +613,22 @@ export class FLineage extends FRoot { this.progressElement.setAttribute("width", "500px"); this.progressElement.innerHTML = "No data to display"; } - - // console.timeEnd("Total duration"); - // console.groupEnd(); } isSafari() { return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); } - /* eslint-disable @typescript-eslint/no-unused-vars */ - /* eslint-disable @typescript-eslint/ban-ts-comment */ - // @ts-ignore - doTemplateHotUpdate(node: LineageNodeElement, isChildNode = false) { + doTemplateHotUpdate( + node: LineageNodeElement, + nodeSVGElement: HTMLElement | null, + isChildNode = false + ): void { try { if (isChildNode) { - if (node.fNodeTemplate) { - return getComputedHTML(node.fNodeTemplate(node)); - } else { - return this["children-node-template"] - ? getComputedHTML(this["children-node-template"](node)) - : ``; + if (node.fNodeTemplate && nodeSVGElement) { + render(node.fNodeTemplate(node), nodeSVGElement); + } else if (this["children-node-template"] && nodeSVGElement) { + render(this["children-node-template"](node), nodeSVGElement); } } else { if (node.fChildren) { @@ -641,28 +637,35 @@ export class FLineage extends FRoot { } else { node.childrenToggle = html``; } - if (node.fNodeTemplate) { - return getComputedHTML(node.fNodeTemplate(node)); - } else { - return this["node-template"] ? getComputedHTML(this["node-template"](node)) : ``; + if (node.fNodeTemplate && nodeSVGElement) { + render(node.fNodeTemplate(node), nodeSVGElement); + } else if (this["node-template"] && nodeSVGElement) { + render(this["node-template"](node), nodeSVGElement); } } } catch (error: unknown) { - console.error(`Error reading node ${node.id}.fData`); - return ` Error reading node ${node.id}.fData - - `; + console.error(`Error reading node ${node.id}.fData`, error); + if (nodeSVGElement) { + render( + html` + Error reading node ${node.id}.fData + `, + nodeSVGElement + ); + } } } diff --git a/stories/flow-lineage/htmlinjection-test.stories.ts b/stories/flow-lineage/htmlinjection-test.stories.ts new file mode 100644 index 000000000..fbcd0ae47 --- /dev/null +++ b/stories/flow-lineage/htmlinjection-test.stories.ts @@ -0,0 +1,349 @@ +import { LineageNodeElement } from "@cldcvr/flow-lineage"; +import { html } from "lit-html"; +import { createRef, ref } from "lit/directives/ref.js"; + +export default { + title: "@cldcvr/flow-lineage/Debug/HTML-injection-test", + + parameters: { + controls: { + hideNoControlsWarning: true + } + } +}; + +export const Playground = { + render: () => { + const links = [ + { + from: "Tony", + to: "Charlie" + }, + { + from: "Charlie", + to: "Tony" + }, + { + from: "Max", + to: "Charlie" + }, + { + from: "Calvin", + to: "Hansen" + }, + { + from: "Brooke", + to: "Tamara" + }, + { + from: "Calvin", + to: "Bradley" + }, + { + from: "Tamara", + to: "Bradley" + }, + { + from: "Charlie", + to: "Roger" + }, + { + from: "Charlie", + to: "Brooke" + }, + { + from: "Bradley", + to: "John" + }, + { + from: "Bradley", + to: "Tim" + }, + { + from: "Bradley", + to: "Sam" + }, + { + from: "Bradley", + to: "Rex" + }, + { + from: "Rex", + to: "Bradley" + }, + { + from: "Roger", + to: "Calvin" + }, + { + from: "Charlie", + to: "Alicen" + }, + { + from: "Alicen", + to: "Jennifer" + }, + { + from: "Jennifer", + to: "John" + }, + { + from: "child1", + to: "Tim" + }, + { + from: "Rex", + to: "Pat" + }, + { + from: "A", + to: "B" + }, + { + from: "Brooke", + to: "child1" + }, + { + from: "mchild1", + to: "child2" + } + ]; + + const nodes = { + Calvin: { + fData: { + email: "abc@cldcvr.com", + mobile: ``, + name: `` + } + }, + + Hansen: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Hansen" + }, + + fChildren: { + hchild1: {} + } + }, + + Alicen: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Alicen" + } + }, + + Jennifer: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Jennifer" + } + }, + + Brooke: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Brooke" + } + }, + + Tamara: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Tamara" + } + }, + + Bradley: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Bradley" + } + }, + + Roger: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Roger" + } + }, + + Max: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Max" + }, + + fHideChildren: false, + + fChildren: { + mchild1: {}, + mchild2: {}, + mchild3: {}, + mchild4: {}, + mchild5: {}, + mchild6: {}, + mchild7: {}, + mchild8: {}, + mchild9: {}, + mchild10: {}, + mchild11: {}, + mchild12: {}, + mchild13: {}, + mchild14: {}, + mchild15: {}, + mchild16: {} + } + }, + + Tony: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Tony" + } + }, + + Charlie: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Charlie" + } + }, + + John: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "John" + } + }, + + Tim: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Tim" + } + }, + + Sam: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Sam" + } + }, + + Rex: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Rex" + } + }, + + Pat: { + fData: { + email: "abc@cldcvr.com", + mobile: "+91-12345 67890", + name: "Pat" + } + } + }; + let idx = 0; + setTimeout(() => { + idx += 1; + nodes.Max.fData.name = `Max-${idx}`; + }, 1000); + setTimeout(() => { + idx += 1; + nodes.Roger.fData = { + email: "abc@cldcvr.com", + mobile: "This is my mobile number", + name: "Roger Updated" + }; + }, 2000); + const nodeTemplate = function (node: LineageNodeElement) { + return html` + + + + + + + ${node.fData?.name} + + ${node.childrenToggle} + `; + }; + + const childNodeTemplate = function (node: LineageNodeElement) { + return html` + + ${node.id} + `; + }; + + const divRef = createRef(); + + return html` + `; + }, + height: "500px" +}; diff --git a/stories/flow-lineage/lineage-properties.mdx b/stories/flow-lineage/lineage-properties.mdx index 65f92a954..9fc3c84a0 100644 --- a/stories/flow-lineage/lineage-properties.mdx +++ b/stories/flow-lineage/lineage-properties.mdx @@ -500,13 +500,13 @@ node-id-1: { //Unique node id for each node Each node is identified by a unique ID. -
+
-
Node example
+
Node example
-
Node data example
+
Node data example
 			{`tony: {                       //Unique node ID
 	fData: {
@@ -524,13 +524,13 @@ Each node is identified by a unique ID.
 
 Nodes are represented through templates, you can pass custom markup to create a custom nodes.
 
-
+
-
Node example
+
Node example
-
Node data example
+
Node data example
 			{`tony: {
 	fNodeTemplate:\`
@@ -552,13 +552,13 @@ The data required by each node needs to be present in the node schema.
 
 ###### Note: Use “\$\{node.data.key}” to access fData in your node template.
 
-
+
-
Node example
+
Node example
-
Node data example
+
Node data example
 			{`tony: {
 	fData: {	//Data goes here
@@ -577,13 +577,13 @@ The data required by each node needs to be present in the node schema.
 
 Each node can have fChildren.
 
-
+
-
Node example
+
Node example
-
Node data example
+
Node data example
 			
 				{`tony: {                       //Unique node ID
@@ -613,12 +613,12 @@ Boolean that defines whether node children will be visible on load or not.
 
 ###### Note: On load, all node children are collpased/hidden. Clicking on a node will reveal its children
 
-
+
-
Node example
+
Node example
-
Node data example
+
Node data example
add code here