diff --git a/package.json b/package.json index 58af87c845..72466175cb 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ } }, "dependencies": { + "dompurify": "^3.1.7", "html-react-parser": "^5.1.17", "react-quill": "^2.0.0" } diff --git a/services/common/src/components/forms/RenderRichTextEditor.tsx b/services/common/src/components/forms/RenderRichTextEditor.tsx index 328f3bd6a4..24e11ef82c 100644 --- a/services/common/src/components/forms/RenderRichTextEditor.tsx +++ b/services/common/src/components/forms/RenderRichTextEditor.tsx @@ -5,6 +5,7 @@ import { FormContext } from "./FormWrapper"; import ReactQuill from "react-quill"; import "react-quill/dist/quill.snow.css"; import parse from "html-react-parser"; +import DOMPurify from "dompurify"; const RenderRichTextEditor: FC = ({ label, @@ -50,7 +51,7 @@ const RenderRichTextEditor: FC = ({ }; if (!isEditMode) { - return ; + return ; } return ( diff --git a/services/common/src/components/help/HelpGuide.spec.tsx b/services/common/src/components/help/HelpGuide-core.spec.tsx similarity index 91% rename from services/common/src/components/help/HelpGuide.spec.tsx rename to services/common/src/components/help/HelpGuide-core.spec.tsx index 915a9f5d5d..8d76699633 100644 --- a/services/common/src/components/help/HelpGuide.spec.tsx +++ b/services/common/src/components/help/HelpGuide-core.spec.tsx @@ -34,7 +34,7 @@ jest.mock("react-router-dom", () => mockFunction()); describe("HelpGuide", () => { it("renders CORE properly", async () => { - const helpKey = "Not-exists"; + const helpKey = "Not-Exists"; const { findByTestId, findByText } = render( @@ -45,6 +45,9 @@ describe("HelpGuide", () => { const helpButton = await findByTestId("help-open"); fireEvent.click(helpButton); + const defaultContent = await findByText("CORE default content"); + expect(defaultContent).toBeInTheDocument(); + const editButton = await findByText("Edit Help Guide"); fireEvent.click(editButton); diff --git a/services/common/src/components/help/HelpGuide-ms.spec.tsx b/services/common/src/components/help/HelpGuide-ms.spec.tsx new file mode 100644 index 0000000000..f228acbedc --- /dev/null +++ b/services/common/src/components/help/HelpGuide-ms.spec.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { render, fireEvent } from "@testing-library/react"; +import { ReduxWrapper } from "@mds/common/tests/utils/ReduxWrapper"; +import HelpGuide, { HelpGuideContent } from "./HelpGuide"; +import { AUTHENTICATION } from "@mds/common/constants/reducerTypes"; +import { SystemFlagEnum } from "@mds/common/constants"; +import { MS_USER_ACCESS_DATA } from "@mds/common/tests/mocks/dataMocks"; +import { helpReducerType } from "@mds/common/redux/slices/helpSlice"; +import { BrowserRouter } from "react-router-dom"; + +const msState = { + [AUTHENTICATION]: { + systemFlag: SystemFlagEnum.ms, + userAccessData: MS_USER_ACCESS_DATA, + isAuthenticated: true, + }, + [helpReducerType]: { + helpGuides: {}, + }, +}; + +function mockFunction() { + const original = jest.requireActual("react-router-dom"); + return { + ...original, + useParams: jest.fn().mockReturnValue({ + tab: "overview", + }), + }; +} + +jest.mock("react-router-dom", () => mockFunction()); + +describe("HelpGuide", () => { + it("renders MS properly with default content", async () => { + const helpKey = "Not-Exists"; + const { findByTestId } = render( + + + + + + + ); + const helpButton = await findByTestId("help-open"); + fireEvent.click(helpButton); + + const helpContent = await findByTestId("help-content"); + + expect(helpContent).toMatchSnapshot(); + }); +}); diff --git a/services/common/src/components/help/HelpGuide.tsx b/services/common/src/components/help/HelpGuide.tsx index 1d636c3927..26235b21af 100644 --- a/services/common/src/components/help/HelpGuide.tsx +++ b/services/common/src/components/help/HelpGuide.tsx @@ -23,6 +23,7 @@ import HelpGuideForm from "./HelpGuideForm"; import { deleteConfirmWrapper } from "../common/ActionMenu"; import { formatSnakeCaseToSentenceCase } from "@mds/common/redux/utils/helpers"; import parse from "html-react-parser"; +import DOMPurify from "dompurify"; import { useFeatureFlag } from "@mds/common/providers/featureFlags/useFeatureFlag"; import { Feature } from "@mds/common/utils"; import Loading from "../common/Loading"; @@ -132,7 +133,7 @@ export const HelpGuideContent: FC = ({ helpKey }) => { pageTab={pageTab} /> ) : ( - parse(content) + parse(DOMPurify.sanitize(content)) ); return ( diff --git a/services/common/src/components/help/__snapshots__/HelpGuide.spec.tsx.snap b/services/common/src/components/help/__snapshots__/HelpGuide-core.spec.tsx.snap similarity index 100% rename from services/common/src/components/help/__snapshots__/HelpGuide.spec.tsx.snap rename to services/common/src/components/help/__snapshots__/HelpGuide-core.spec.tsx.snap diff --git a/services/common/src/components/help/__snapshots__/HelpGuide-ms.spec.tsx.snap b/services/common/src/components/help/__snapshots__/HelpGuide-ms.spec.tsx.snap new file mode 100644 index 0000000000..0ae7b374bc --- /dev/null +++ b/services/common/src/components/help/__snapshots__/HelpGuide-ms.spec.tsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HelpGuide renders MS properly with default content 1`] = ` +
+

+ Overview + Help Guide +

+

+ console.log('should not render as inline function');}>MS default content +

+
+`; diff --git a/services/common/src/tests/handlers.ts b/services/common/src/tests/handlers.ts index 20680306a0..c64f9dd9d5 100644 --- a/services/common/src/tests/handlers.ts +++ b/services/common/src/tests/handlers.ts @@ -33,7 +33,8 @@ const helpHandler = http.get("/%3CAPI_URL%3E/help/:helpKey", async ({ request, p const guideData = system === SystemFlagEnum.core ? HELP_GUIDE_CORE : HELP_GUIDE_MS; - return HttpResponse.json(guideData[helpKey as string]); + const response = { records: [...guideData[helpKey as string]] }; + return HttpResponse.json(response); }); const commonHandlers = [...geoSpatialHandlers, ...projectHandlers, helpHandler]; diff --git a/services/common/src/tests/mocks/dataMocks.tsx b/services/common/src/tests/mocks/dataMocks.tsx index a4b04aee12..b822c2eec9 100644 --- a/services/common/src/tests/mocks/dataMocks.tsx +++ b/services/common/src/tests/mocks/dataMocks.tsx @@ -1500,6 +1500,12 @@ export const USER_ACCESS_DATA = [ "role_edit_project_summaries", ]; +export const MS_USER_ACCESS_DATA = [ + "mds_minespace_proponents", + "c_mds_users", + "c_mds_minespace_proponent", +]; + export const DISTURBANCE_OPTIONS = { records: [ { @@ -8834,7 +8840,8 @@ export const HELP_GUIDE_MS = { default: [ { help_guid: "123", - content: "

MS default content

", + content: + "

console.log('should not render as inline function');}>MS default content

", tab: "all_tabs", help_key: "default", system: SystemFlagEnum.ms, diff --git a/services/core-api/app/api/help/resources/help_resource.py b/services/core-api/app/api/help/resources/help_resource.py index b80f6b8d9f..69346979a2 100644 --- a/services/core-api/app/api/help/resources/help_resource.py +++ b/services/core-api/app/api/help/resources/help_resource.py @@ -34,7 +34,7 @@ class HelpListResource(Resource, UserMixin): @requires_any_of([VIEW_ALL, MINESPACE_PROPONENT]) def get(self): data = self.parser.parse_args() - system = data.get("system", None) if not is_minespace_user() else SystemFlag.ms + system = data.get("system", None) if not is_minespace_user() else str(SystemFlag.ms) help_guides = Help.get_all(system) return help_guides @@ -82,7 +82,7 @@ class HelpResource(Resource, UserMixin): @api.marshal_with(HELP_MODEL, code=200, envelope='records', as_list=True) def get(self, help_key): system = request.args['system'] - if is_minespace_user() and system != SystemFlag.ms: + if is_minespace_user() and system != str(SystemFlag.ms): raise BadRequest("Only MineSpace help guides may be requested.") help_guides = Help.find_by_help_key(help_key, system) return help_guides diff --git a/yarn.lock b/yarn.lock index b7d044e2df..fb5f4a4192 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6885,6 +6885,13 @@ __metadata: languageName: node linkType: hard +"dompurify@npm:^3.1.7": + version: 3.1.7 + resolution: "dompurify@npm:3.1.7" + checksum: 0a9b811bbc94f3dba60cf6486962362b0f1a5b4ab789f5e1cbd4749b6ba1a1fad190a677a962dc8850ce28764424765fe425e9d6508e4e93ba648ef15d54bc24 + languageName: node + linkType: hard + "domutils@npm:^2.5.2, domutils@npm:^2.8.0": version: 2.8.0 resolution: "domutils@npm:2.8.0" @@ -12879,6 +12886,7 @@ __metadata: dependencies: "@typescript-eslint/eslint-plugin": ^5.59.1 "@typescript-eslint/parser": ^5.59.1 + dompurify: ^3.1.7 eslint: 7.27.0 eslint-config-airbnb: 18.2.1 eslint-config-airbnb-typescript: ^17.0.0