From 4369366a625d39045e493c97a4c96b82f7edd068 Mon Sep 17 00:00:00 2001 From: Spyros Date: Mon, 8 Jan 2024 23:10:01 +0000 Subject: [PATCH] 473 deeplink fix (#474) * fix deeplink * deep link in web main * lint --------- Signed-off-by: Spyros --- .../frontend/src/pages/Deeplink/Deeplink.tsx | 68 +++++++---- application/tests/web_main_test.py | 111 ++++++++++++++++-- application/web/web_main.py | 32 +++++ 3 files changed, 178 insertions(+), 33 deletions(-) diff --git a/application/frontend/src/pages/Deeplink/Deeplink.tsx b/application/frontend/src/pages/Deeplink/Deeplink.tsx index b0d65da7f..aeae5efcd 100644 --- a/application/frontend/src/pages/Deeplink/Deeplink.tsx +++ b/application/frontend/src/pages/Deeplink/Deeplink.tsx @@ -5,9 +5,10 @@ import { useLocation, useParams } from 'react-router-dom'; import { LoadingAndErrorIndicator } from '../../components/LoadingAndErrorIndicator'; import { useEnvironment } from '../../hooks'; import { Document } from '../../types'; +import { Standard } from '../Standard/Standard'; export const Deeplink = () => { - let { type, nodeName, section, subsection, tooltype, sectionID } = useParams(); + let { type, nodeName, section, subsection, tooltype, sectionID, sectionid } = useParams(); const { apiUrl } = useEnvironment(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -17,26 +18,36 @@ export const Deeplink = () => { subsection = subsection ? subsection : new URLSearchParams(search).get('subsection'); tooltype = tooltype ? tooltype : new URLSearchParams(search).get('tooltype'); sectionID = sectionID ? sectionID : new URLSearchParams(search).get('sectionID'); + sectionid = sectionID ? sectionID : new URLSearchParams(search).get('sectionid'); if (!type) { // Backwards compatible fix, the url used to be /deeplink/:nodename, new url is /deeplink/:type/:nodename type = 'Standard'; } - var url = - `${apiUrl}/${type}/${nodeName}` + - (section != null ? `?section=${section}&` : '') + - (subsection != null ? `subsection=${subsection}&` : '') + - (tooltype != null ? `tooltype=${tooltype}&` : '') + - (sectionID != null ? `sectionID=${sectionID}&` : ''); + var apiCall = new URL(`${apiUrl}/${type}/${nodeName}?`); + if (section != null) { + apiCall.searchParams.append('section', section); + } + if (subsection != null) { + apiCall.searchParams.append('subsection', subsection); + } + if (tooltype != null) { + apiCall.searchParams.append('tooltype', tooltype); + } + if (sectionID != null) { + apiCall.searchParams.append('sectionID', sectionID); + } else if (sectionid != null) { + apiCall.searchParams.append('sectionID', sectionid); + } useEffect(() => { window.scrollTo(0, 0); setLoading(true); axios - .get(url) + .get(apiCall.toString()) .then(function (response) { setError(null); - setData(response.data?.standard); + setData(response.data?.standards); }) .catch(function (axiosError) { if (axiosError.response.status === 404) { @@ -49,25 +60,32 @@ export const Deeplink = () => { setLoading(false); }); }, [type, nodeName]); - const documents = data || []; - return ( - <> + + var redirectTo = window.location.href; + if (documents) { + for (const standard of documents) { + if (standard.hyperlink && standard.hyperlink?.length > 0) { + redirectTo = standard.hyperlink; + } + } + } + if (!error && !loading && redirectTo != window.location.href) { + return ( +
+

{nodeName}

+ +

Redirecting to:

+ {window.location.href} + {(window.location.href = redirectTo)} +
+ ); + } else { + return (

{nodeName}

- {!error && - !loading && - documents.map( - (standard, i) => - // console.log( (standard && standard.hyperlink && standard.hyperlink.length > 0) ? standard.hyperlink : window.location.href) - (window.location.href = - standard && standard.hyperlink && standard.hyperlink.length > 0 - ? standard.hyperlink - : window.location.href) - )}
- ; - - ); + ); + } }; diff --git a/application/tests/web_main_test.py b/application/tests/web_main_test.py index 2621386fc..d1f144df6 100644 --- a/application/tests/web_main_test.py +++ b/application/tests/web_main_test.py @@ -1,3 +1,5 @@ +import random +import string import re import json import unittest @@ -80,15 +82,18 @@ def test_extend_cre_with_tag_links(self) -> None: ) ), "cc": cres["cc"], - "cd": cres["cd"] - .add_link( - defs.Link( - ltype=defs.LinkTypes.PartOf, document=cres["ca"].shallow_copy() + "cd": ( + cres["cd"] + .add_link( + defs.Link( + ltype=defs.LinkTypes.PartOf, document=cres["ca"].shallow_copy() + ) ) - ) - .add_link( - defs.Link( - ltype=defs.LinkTypes.Contains, document=cres["cb"].shallow_copy() + .add_link( + defs.Link( + ltype=defs.LinkTypes.Contains, + document=cres["cb"].shallow_copy(), + ) ) ), } @@ -711,3 +716,93 @@ def test_gap_analysis_weak_links_response(self, db_mock) -> None: ) self.assertEqual(200, response.status_code) self.assertEqual(expected, json.loads(response.data)) + + def test_deeplink(self) -> None: + self.maxDiff = None + collection = db.Node_collection() + with self.app.test_client() as client: + response = client.get( + f"/rest/v1/deeplink/{''.join(random.choice(string.ascii_letters) for i in range(10))}", + ) + self.assertEqual(404, response.status_code) + + cres = { + "ca": defs.CRE(id="1", description="CA", name="CA", tags=["ta"]), + "cd": defs.CRE(id="2", description="CD", name="CD", tags=["td"]), + "cb": defs.CRE(id="3", description="CB", name="CB", tags=["tb"]), + } + standards = { + "cwe0": defs.Standard(name="CWE", sectionID="456"), + "ASVS": defs.Standard( + name="ASVS", + section="sectionASVS", + sectionID="v0.1.2", + hyperlink="https://github.com/owasp/asvs/blah", + ), + } + cres["ca"].add_link( + defs.Link( + ltype=defs.LinkTypes.Contains, document=cres["cd"].shallow_copy() + ) + ) + cres["cb"].add_link( + defs.Link( + ltype=defs.LinkTypes.Contains, document=cres["cd"].shallow_copy() + ) + ) + cres["cd"].add_link(defs.Link(document=standards["cwe0"])) + cres["cb"].add_link(defs.Link(document=standards["ASVS"])) + + dca = collection.add_cre(cres["ca"]) + dcb = collection.add_cre(cres["cb"]) + dcd = collection.add_cre(cres["cd"]) + dasvs = collection.add_node(standards["ASVS"]) + dcwe = collection.add_node(standards["cwe0"]) + collection.add_internal_link( + group=dca, cre=dcd, type=defs.LinkTypes.Contains + ) + collection.add_internal_link( + group=dcb, cre=dcd, type=defs.LinkTypes.Contains + ) + + collection.add_link(dcb, dasvs) + collection.add_link(dcd, dcwe) + + response = client.get("/rest/v1/deeplink/CWE?sectionid=456") + self.assertEqual(404, response.status_code) + + response = client.get("/rest/v1/deeplink/ASVS?sectionid=v0.1.2") + location = "" + for head in response.headers: + if head[0] == "Location": + location = head[1] + self.assertEqual(location, standards["ASVS"].hyperlink) + self.assertEqual(302, response.status_code) + + response = client.get("/rest/v1/deeplink/ASVS?sectionID=v0.1.2") + location = "" + for head in response.headers: + if head[0] == "Location": + location = head[1] + self.assertEqual(location, standards["ASVS"].hyperlink) + self.assertEqual(302, response.status_code) + + response = client.get( + f'/rest/v1/deeplink/ASVS?section={standards["ASVS"].section}' + ) + location = "" + for head in response.headers: + if head[0] == "Location": + location = head[1] + self.assertEqual(location, standards["ASVS"].hyperlink) + self.assertEqual(302, response.status_code) + + response = client.get( + f'/rest/v1/deeplink/ASVS?section={standards["ASVS"].section}§ionID={standards["ASVS"].sectionID}' + ) + location = "" + for head in response.headers: + if head[0] == "Location": + location = head[1] + self.assertEqual(location, standards["ASVS"].hyperlink) + self.assertEqual(302, response.status_code) diff --git a/application/web/web_main.py b/application/web/web_main.py index af8459286..c78730cda 100644 --- a/application/web/web_main.py +++ b/application/web/web_main.py @@ -515,6 +515,38 @@ def smartlink( return abort(404, "Document does not exist") +@app.route("/rest/v1/deeplink//", methods=["GET"]) +@app.route("/rest/v1/deeplink/", methods=["GET"]) +def deeplink(name: str, ntype: str = "") -> Any: + database = db.Node_collection() + opt_section = request.args.get("section") + opt_sectionID = request.args.get("sectionID") + opt_sectionid = request.args.get("sectionid") + opt_version = request.args.get("version") + opt_subsection = request.args.get("subsection") + + if opt_section: + opt_section = urllib.parse.unquote(opt_section) + if opt_sectionID: + opt_sectionID = urllib.parse.unquote(opt_sectionID) + elif opt_sectionid: + opt_sectionID = urllib.parse.unquote(opt_sectionid) + + nodes = None + nodes = database.get_nodes( + name=name, + section=opt_section, + subsection=opt_subsection, + version=opt_version, + sectionID=opt_sectionID, + ) + for node in nodes: + if len(node.hyperlink) > 0: + return redirect(node.hyperlink) + + return abort(404) + + @app.before_request def before_request(): if os.environ.get("INSECURE_REQUESTS"):